Haskell的缩进

关于Haskell代码的缩进,在 real world Haskell以及其他 Haskell的教程中只泛泛介绍了一些,还是有些让人迷惑。有人推荐了一篇网文,专门介绍Haskell的缩进,说的比较详细。原文在此:http://en.wikibooks.org/wiki/Haskell/Indentation 我试着翻译了一下,如下。

Haskell缩进

    Haskell依靠缩进来简化冗长的代码,但它的缩进规则会让人有点儿迷惑,看起来很多而且很随意。其实混乱来自于规则在实际代码中的应用,而规则本身很简单,只有一两条。所以我们抛开实际中的缩进与排版的混乱表象,来看看规则的本质。
    1 缩进的黄金规则
    尽管本文要详细讲解Haskell的缩进体系,但是请记住一个最基本的规则:作为表达式的一部分代码,一定要比表达式的开头再缩进一些。
    这是什么意思?看一个简单的let绑定表达式的例子。绑定变量的等式是let表达式的一部分,因此它要比作为表达式开头的let关键字更缩进一些。
.-----------------------------
|   let
|     x = a
|     y = b
------------------------------
    你当然可以只缩进一个空格,不过一般都会把第一个等式放在let的同一行,并把其余行缩进到与第一个等式对齐:
.-----------------------------
|   let x = a
|       y = b
------------------------------
    还有更多的例子:
.-----------------------------
|   do foo
|      bar
|      baz
|    
|   where x = a
|         y = b
|
|   case x of
|       p  -> foo
|       p' -> baz
 ------------------------------   
    注意不像do和where表达式,在case表达式中,把第二行放到case的同一行没有意义。还有注意箭头(->)也被对齐了,这只是为了看上去漂亮一些,不是缩进排版所必须的。只有一行开头的空白才会改变排版,造成代码的不同含义。
    如果一个表达式的开头并没有在一行的开始,缩进就稍微复杂一些。其实后面的代码只需比包含表达式开头的行更缩进一些就行。
.-----------------------------------------------------------------------------------------
|   myFunction firstArgument secondArgument = do --'do'不在一行开始
|       foo                                      --这些行只需比包含'do'的行更缩进一些就行
|       bar
|       baz
------------------------------------------------------------------------------------------
    上面的例子也可以这样写:
.-------------------------------------------------------
|   myFunction firstArgument secondArgument =
|       do foo
|          bar
|          baz
--------------------------------------------------------
    或者:
.-------------------------------------------------------
|   myFunction firstArgument secondArgument = do foo
|                                                bar
|                                                baz
 -------------------------------------------------------                                                
    2 两种风格的转换 
    信不信缩进排版也不是必须的?是的,像C语言一样,Haskell也可以用分号来分割语句,用花括号来组织代码块。这种形式不仅常用,而且通过学习如何转换两种排版风格也有助于理解Haskell的缩进规则。首先要理解两个事情:何时需要分号和花括号,如何根据排版来转换。转换过程可以总结为3条规则(加上一条不太常用的第4条):
    (1)遇到一个排版关键词(let,where,of,do),加一个左花括号。
    (2)遇到具有相同缩进的句子,加一个分号。
    (3)遇到一个退回缩进的句子,加一个右花括号。
    (4)在list中遇到异常的句子,如where,不加分号,改加成右花括号。
    练习1:用一个词回答:遇到一个更为缩进的句子时该加什么?
    练习2:将下面的缩进排版翻译成分号花括号排版。注:有些语句可能不是Haskell的合法语句,本练习只是为了加强对翻译过程的理解。
    of a
       b
        c
       d
    where
    a
    b
    c
    do
   you
    like
   the
  way
   i let myself
          abuse
         these
   layout rules

    3 试试排版
.-------------------------
|错误的
|-------------------------
|if foo
|   then do first thing
|        second thing
|        third thing
|   else do something else
---------------------------

.-------------------------
|正确的
|-------------------------
|if foo
|   then do first thing
|           second thing
|           third thing
|   else do something else
---------------------------

    if中的do
    当if中出现do表达式时该怎么做?上面说过了,任何除过那4个排版关键字的符号,包括if then else,都不影响排版。所以缩进如下:
.-------------------------
|错误的
|-------------------------
|if foo
|   then do first thing
|        second thing
|        third thing
|   else do something else
---------------------------

.-------------------------
|正确的
|-------------------------
|if foo
|   then do first thing
|           second thing
|           third thing
|   else do something else
---------------------------

    只要比第一行更缩进
    记着,根据黄金规则,尽管do关键字告诉Haskell插入一个左花括号,花括号却是取决于do后面所跟的语句。下面这个看起来怪怪的代码块也是正确的:
        do
    first thing
    second thing
    third thing

    也可以把if-do的联合写成这样:
.-------------------------
|错误的
|-------------------------
|if foo
|   then do first thing
|        second thing
|        third thing
|   else do something else
---------------------------

.-------------------------
|正确的
|-------------------------
|if foo
|   then do 
|     first thing
|     second thing
|     third thing
|   else do something else
---------------------------

    do中的if
    这段代码绊倒了许多Haskell程序员,看看为什么代码块是错的呢?

    --为什么是错的?
    do first thing
        if condition
        then foo
        else bar
        third thing

    强调一下,这段代码中的if then else没有错,问题是当在do代码块中时,do注意到then语句部分和if语句部分有相同的缩进,导致它认为then语句是一个和if并列的新的语句。去掉语法糖,这就和其下边的写法一样:
.-------------------------
|一般排版
|-------------------------
|--错误版本
|do first thing
|   if condition
|   then foo
|   else bar
|   third thing
---------------------------

.-------------------------
|去掉语法糖后
|-------------------------
|--错误版本
|do { first thing
|   ; if condition
|   ; then foo
|   ; else bar
|   ; third thing }
---------------------------

    这种写法使得编译器只看到了if condition;一样的语句,认为if表达式没有被完成,因此难怪会出错。改正方法就是把if块中的其他语句向后缩进一些,如下:
.-------------------------
|一般排版
|-------------------------
|--修正后的版本
|do first thing
|   if condition
|     then foo
|     else bar
|   third thing
---------------------------

.-------------------------
|去掉语法糖后
|-------------------------
|--修正后的版本
|do { first thing
|   ; if condition
|       then foo
|       else bar
|   ; third thing }
---------------------------

    if后的缩进防止了do代码块错误的将then部分解释为一个新的语句。你也可以对每一个then else进行这样的缩进,虽然有时候这并不是必须的,但会避免很多歧义,使程序更清晰。
    练习:do中if的缩进问题困扰了很多Haskell编程者,有人向Haskell的主要设计人员提议在if then else中也增加分号,你觉得这会有什么样的作用?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章