考虑范围的格式化 Haskell 函数的正确方法?
Correct way to format Haskell functions considering scope?
我是 Haskell 的新手。我整理了一个基本的凯撒密码,它可以工作,但它非常混乱且难以阅读。
caesarCipher :: Int -> String -> String
caesarCipher n xs = [shift n x | x <- xs]
shift n c = num2let ((let2num c + n) `mod` 26)
alphabet = ['a'..'z']
let2num c = head[ b | (a,b) <- zip alphabet [0..length alphabet], a==c]
num2let = (!!) alphabet
在 Haskell 中格式化由多个变量和表达式组成的函数的 "correct" 方法是什么,我是否应该考虑 scope变量?除了基于效率的建议之外,我还犯过任何其他 "major" 错误吗?
这是我的尝试:
caesarCipher n xs = let
shift n c = num2let ((let2num c + n) `mod` 26) where
alphabet = ['a'..'z']
let2num c = head[ b | (a,b) <- zip alphabet [0..length alphabet], a==c]
num2let = (!!) alphabet
in [shift n x | x <- xs]
我会首先重写一些函数。例如。 zip alphabet [0 .. length alphabet]
可以用 zip alphabet [0..]
代替,因为 zip
会在其中一个列表用完时停止。使用 (!!)
和 head
通常不是好的做法,因为这些函数是非总的:如果索引太大,或者列表为空,(!!)
和 head
会分别报错。
我们可以定义辅助函数,例如 num2let
:
import Data.Char(chr, ord)
num2let :: Int -> Char
num2let n = chr (n + ord 'a')
此处num2let
会将0
映射到'a'
,1
映射到'b'
,等等
let2num
可以用类似的方式完成:
import Data.Char(ord)
let2num :: Char -> Int
let2num c = ord c - ord 'a'
所以现在我们可以将caesarCipher
定义为:
caesarCipher :: Int -> String -> String
caesarCipher n = map (num2let . (`mod 26`) . (n+) . let2num)
这样看起来完整:
import Data.Char(chr, ord)
num2let :: Int -> Char
num2let n = chr (n + ord 'a')
let2num :: Char -> Int
let2num c = ord c - ord 'a'
caesarCipher :: Int -> String -> String
caesarCipher n = map (num2let . (`mod` 26) . (n+) . let2num)
好的是,您可以在这里为其他功能重用 let2num
和 num2let
。
通常顶层函数用一个空行分隔,并给出一个签名。这不是必需的,但通常更方便阅读。
我是 Haskell 的新手。我整理了一个基本的凯撒密码,它可以工作,但它非常混乱且难以阅读。
caesarCipher :: Int -> String -> String
caesarCipher n xs = [shift n x | x <- xs]
shift n c = num2let ((let2num c + n) `mod` 26)
alphabet = ['a'..'z']
let2num c = head[ b | (a,b) <- zip alphabet [0..length alphabet], a==c]
num2let = (!!) alphabet
在 Haskell 中格式化由多个变量和表达式组成的函数的 "correct" 方法是什么,我是否应该考虑 scope变量?除了基于效率的建议之外,我还犯过任何其他 "major" 错误吗?
这是我的尝试:
caesarCipher n xs = let
shift n c = num2let ((let2num c + n) `mod` 26) where
alphabet = ['a'..'z']
let2num c = head[ b | (a,b) <- zip alphabet [0..length alphabet], a==c]
num2let = (!!) alphabet
in [shift n x | x <- xs]
我会首先重写一些函数。例如。 zip alphabet [0 .. length alphabet]
可以用 zip alphabet [0..]
代替,因为 zip
会在其中一个列表用完时停止。使用 (!!)
和 head
通常不是好的做法,因为这些函数是非总的:如果索引太大,或者列表为空,(!!)
和 head
会分别报错。
我们可以定义辅助函数,例如 num2let
:
import Data.Char(chr, ord)
num2let :: Int -> Char
num2let n = chr (n + ord 'a')
此处num2let
会将0
映射到'a'
,1
映射到'b'
,等等
let2num
可以用类似的方式完成:
import Data.Char(ord)
let2num :: Char -> Int
let2num c = ord c - ord 'a'
所以现在我们可以将caesarCipher
定义为:
caesarCipher :: Int -> String -> String
caesarCipher n = map (num2let . (`mod 26`) . (n+) . let2num)
这样看起来完整:
import Data.Char(chr, ord)
num2let :: Int -> Char
num2let n = chr (n + ord 'a')
let2num :: Char -> Int
let2num c = ord c - ord 'a'
caesarCipher :: Int -> String -> String
caesarCipher n = map (num2let . (`mod` 26) . (n+) . let2num)
好的是,您可以在这里为其他功能重用 let2num
和 num2let
。
通常顶层函数用一个空行分隔,并给出一个签名。这不是必需的,但通常更方便阅读。