Haskell 很丑但并不是真的丑

我在网上看到的大多数 Haskell 代码都很丑,但这并不是语言的原因。真正的原因是人们缩进以及命名变量的方式。Haskell 用缩进作为语法的一部分,但不像 Python,Haskell 并不强制缩进宽度一致。这给了你一些格式上的自由,但这也给了没品味的人同样的自由。

拿这个做例子来说(来自 Haskell wikibook):

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   if (read guess) < num
     then do putStrLn "Too low!"
             doGuessing num
     else if (read guess) > num
            then do putStrLn "Too high!"
                    doGuessing num
            else putStrLn "You Win!"

这就只是 10 行简单代码,但仍然又丑又难读。为什么?

缩进总体上就是不一致。他们给这两行用 3 个空格缩进:

doGuessing num = do
   putStrLn "Enter your guess:"

但是给这两行用 2 个空格缩进:

   if (read guess) < num
     then do putStrLn "Too low!"

他们还继承了 SICP 里面那个愚蠢的缩进风格,这使得这里的 doGuessing 那行和 then do 那行相差 1 个空格:

     then do putStrLn "Too low!"
             doGuessing num
     else if (read guess) > num
            then do putStrLn "Too high!"

putStrLn 就是个很丑的名字,不幸的是它是个标准库函数。这函数名应该得是 printLine 这样的,看着要漂亮多了,也能和 getLine 一致。

这段代码也缺乏一些垂直空行和水平对齐。

解决这些问题后,代码变成了这样:

doGuessing num = do

    printLine "Enter your guess:"
    guess <- getLine

    if      (read guess) < num then do
        printLine "Too low!"
        doGuessing num
    else if (read guess) > num then do
        printLine "Too high!"
        doGuessing num
    else
        printLine "You Win!"

这只加了 3 行代码(其中 2 行是空行),看起来就好多了。如果你想要“正确”的嵌套,可以这么写,仍然会比原代码好:

doGuessing num = do

    printLine "Enter your guess:"
    guess <- getLine

    if (read guess) < num
        then do
            printLine "Too low!"
            doGuessing num
        else if (read guess) > num
            then do
                printLine "Too high!"
                doGuessing num
            else
                printLine "You Win!"