我还没有弄清楚我的“理想” Haskell 风格。我目前倾向于编写代码的方式可能最受 Python 和 Mercury 编码的影响。
我倾向于喜欢多行结构有一个明显的“标题”行,它告诉我结构是什么而无需仔细阅读(这通常意味着多行结构的“形状”应该由最开始或非常“标题”行的末尾),并具有多行结构的不同“组成部分”,其中几个不同的多行部分通过改变缩进清楚地描绘出来。所以我更有可能把你的例子写成:
funcA :: Integer -> IO ()
funcA n =
if n == 0 then
putStrLn "zero"
else
do putStr "--> "
print n
或者可能:
funcA :: Integer -> IO ()
funcA n =
if n == 0 then
putStrLn "zero"
else do
putStr "--> "
print n
将else
anddo
折叠到同一行(尽管在这种情况下,我在 之后开始新行do
)。类似地,整个定义为do
块的函数通常在函数头中do
紧跟在后面,块的实际代码在缩进的一组行中。=
do
如果 if/then/else 的条件更复杂,我也会给它自己的“部分”,变成:
funcA :: Integer -> IO ()
funcA n =
if
n == 0
then
putStrLn "zero"
else
do putStr "--> "
print n
不过,我很确定这种 if/then/else 格式取决于 GHC 中相对较新的变化;以前then
andelse
部分必须比 . 缩进更多if
。在这条规则下,我从来没有找到一种我非常熟悉的 if/then/else 块的编写方式;幸运的是,由于模式匹配和守卫,它们在 Haskell 中并不常见。
我使用这种风格的方式有些不一致,我还不完全满意。例如,在我当前打开的文件中,我有以下形式的几个函数:
foo a b = simple expression c d
where
c = bar a
d = baz b
看起来不错,但是:
foo a b =
complex multiline expression c d
where
c = bar a
d = baz b
逻辑上where
应该与函数主体处于不同的缩进级别。但它不应该缩进更多,因为它是 的一部分foo a b =
,而不是 的一部分complex ...
。但是我也不想再缩进complex ...
一级来回应,因为跳转到两级缩进看起来很难看,而且它的正确缩进级别由后面是否有where
块来确定是令人不快的。where
幸运的是,当函数的定义是一些辅助定义的简单表达时,我似乎大多使用;如果该函数作为一些辅助定义的大型复杂函数突然出现,我会尝试将其分解为更独立的部分。
我觉得这种风格主要避免了失控的缩进(它也是“重构友好的”,因为它不通过其他代码位的长度来确定代码的正确缩进位置),同时仍然允许我直观地确定我的代码的高级结构无需对底层结构进行详细解析。
我有时担心我的思维方式太像 Pythonista(具有缩进级别)或 Mercuryista(?)(具有具有节的代码结构),而不是采用 Haskell 的“缩进比开始的位置更多”方法。但是一旦事情开始变得复杂,像你的第一个漂亮的例子这样的东西就变得非常不漂亮,而且我的口味不可读。