Haskell代码格式化

dem*_*emi 5 haskell coding-style

Haskell为块提供了基于缩进的样式.我知道两种风格,但我无法确定哪种风格更好.(请原谅我一个非常愚蠢的示例函数)

第1名 - 漂亮:

funcA :: Integer -> IO ()
funcA n = if n == 0
            then putStrLn "zero"
            else do putStr "--> "
                    print n
Run Code Online (Sandbox Code Playgroud)

这种风格看起来很棒,但它非常脆弱:让我们重构这段代码并重命名funcA- 我们需要重新启动then,else以及所有表达式do.同名重命名n.这很烦人.非常.

编辑正如FUZxxl所提到的,线条变得越来越长,但大多数线条 - 开头的空格.

另一种风格是重构友好,但不是那么漂亮:

funcA :: Integer -> IO ()
funcA n = if n == 0
    then putStrLn "zero"
    else do
        putStr "zero"
        print n
Run Code Online (Sandbox Code Playgroud)

你更喜欢什么样的风格?为什么?可能你有另一个或者你有一些链接到代码风格的一些伟大的开发人员解释?

fuz*_*fuz 5

就个人而言,我使用两种风格的混合.

当下一个"漂亮"的缩进层不太远时,我使用第一种样式

do foo
   bar
   baz
Run Code Online (Sandbox Code Playgroud)

但是当一个漂亮的缩进会导致代码在右侧太远时,我只使用两个空格

case foo of
  a -> bar
  b -> baz
  c -> quux
Run Code Online (Sandbox Code Playgroud)

当我有一个超过20个空格的缩进时,我经常"重置"缩进级别:

do a
   b <- do c -- normally I use only two whitespaces here
           d
           e <- do
     f
     g
     h
Run Code Online (Sandbox Code Playgroud)

如果您可以将关键字后面的第一个列表放在关键字之后,我只会在使用漂亮样式时执行此操作

 main = do x
           y
           z where
   a = b
   c = d
   e = f
Run Code Online (Sandbox Code Playgroud)

你的榜样

funcA :: Integer -> IO ()
funcA n = if n == 0
  then putStrLn "zero"
  else do putStr "--> "
          print n
Run Code Online (Sandbox Code Playgroud)

但是,在这个例子中,我将重构代码以使用模式匹配和显式monadic操作(在本例中>>),替换do.如果do是短的,不要这样做.

funcA :: Integer -> IO ()
funcA 0 = putStrLn "zero"
funcA n = putStr "--> " >> print n
Run Code Online (Sandbox Code Playgroud)


Ben*_*Ben 2

I still haven't figured out my "ideal" Haskell style. The way I tend to write code currently is probably most heavily influenced by coding in Python and Mercury.

I tend to like multi-line structures to have an obvious "header" line that tells me what the structure is without a close reading (this generally means the "shape" of the multi-line structure should be determined by the very start or very end of the "header" line), and to have the different "component parts" of a multi-line structure with several distinct multi-line parts clearly delineated by changing indentation. So I would be much more likely to write your example as:

funcA :: Integer -> IO ()
funcA n =
    if n == 0 then
        putStrLn "zero"
    else
        do  putStr "--> "
            print n
Run Code Online (Sandbox Code Playgroud)

Or possibly:

funcA :: Integer -> IO ()
funcA n =
    if n == 0 then
        putStrLn "zero"
    else do
        putStr "--> "
        print n
Run Code Online (Sandbox Code Playgroud)

with the else and do folded onto the same line (though in that case I start a new line after the do). Similarly a function whose entire definition is a do block usually has the do immediately following the = in the function header, and the actual code of the do block following in an indented set of lines.

If the condition of the if/then/else was more complicated, I would give that its own "section" as well, becoming:

funcA :: Integer -> IO ()
funcA n =
    if
        n == 0
    then
        putStrLn "zero"
    else
        do  putStr "--> "
            print n
Run Code Online (Sandbox Code Playgroud)

I'm pretty sure this format of if/then/else depends on a relatively recent change in GHC though; previously the then and else part had to be indented more than the if. I never found a way of writing if/then/else blocks that I was terribly comfortable with under that rule; fortunately they're not super common in Haskell due to pattern-matching and guards.

There are some inconsistencies in the way I use this style that I'm not fully happy with yet. For example, in a file I currently have open I have several functions of the form:

foo a b = simple expression c d
    where
        c = bar a
        d = baz b
Run Code Online (Sandbox Code Playgroud)

Which looks fine, but then:

foo a b =
    complex multiline expression c d
    where
        c = bar a
        d = baz b
Run Code Online (Sandbox Code Playgroud)

The where should logically be at a different indentation level from the main body of the function. But it shouldn't be more indented because it's a part of foo a b =, not a part of complex .... But neither do I want to respond by indenting complex ... one level more, because the jump to two indentation levels looks ugly, and it's unpleasant for its correct indentation level to be determined by whether or not there is a where block afterwards. Fortunately I mostly seem to use where when the definition of the function is a simple expression of some auxiliary definitions; if the function springs to mind as a large complex function of some auxiliary definitions I try to break it down into more independent parts.

I feel this style mostly avoids runaway indentation (it's also "refactor friendly" in terms not determining the correct indent position of code by the length of other bits of code), while still allowing me to visually determine the high-level structure of my code without having to do a detailed parse of the low-level structure.

有时我担心我的想法太像Pythonista(具有缩进级别)或Mercuryista(?)(具有分段的代码结构),而不是采用Haskell的“缩进比开始的位置更多”方法。但一旦事情开始变得复杂,像你的第一个漂亮的例子这样的事情就变得明显不漂亮并且不符合我的口味。