考虑范围的格式化 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)

好的是,您可以在这里为其他功能重用 let2numnum2let

通常顶层函数用一个空行分隔,并给出一个签名。这不是必需的,但通常更方便阅读。