关于关键字:Haskell:Where vs. Let

Haskell: Where vs. Let

我是哈斯凯尔的新手,我对"让哪里"很困惑。它们似乎都有相似的目的。我读过几篇关于Where和Let的比较文章,但是我很难分辨何时使用它们。有人能提供一些上下文或者一些例子来说明什么时候使用一个而不是另一个吗?

Where vs. Let

A where clause can only be defined at the level of a function definition. Usually, that is identical to the scope of let definition. The only difference is when guards are being used. The scope of the where clause extends over all guards. In contrast, the scope of a let expression is only the current function clause and guard, if any.

Haskell备忘单

haskell wiki非常详细,提供了各种各样的案例,但它使用了假设的例子。我觉得它的解释对初学者来说太简单了。

LET的优点:

1
2
3
f :: State s a
f = State $ \x -> y
   where y = ... x ...

控制单态

will not work, because where refers to
the pattern matching f =, where no x
is in scope. In contrast, if you had
started with let, then you wouldn't
have trouble.

haskell wiki关于let的优势

1
2
3
4
f :: State s a
f = State $ \x ->
   let y = ... x ...
   in  y

何处优势:

1
2
3
4
5
6
7
8
9
10
11
12
13
f x
  | cond1 x   = a
  | cond2 x   = g a
  | otherwise = f (h x a)
  where
    a = w x

f x
  = let a = w x
    in case () of
        _ | cond1 x   = a
          | cond2 x   = g a
          | otherwise = f (h x a)

声明与表达式

haskell wiki提到where子句是声明性的,而let表达式是表达性的。除了风格,他们的表现如何不同?

1
2
3
4
5
6
Declaration style                     | Expression-style
--------------------------------------+---------------------------------------------
where clause                          | let expression
arguments LHS:     f x = x*x          | Lambda abstraction: f = \x -> x*x
Pattern matching:  f [] = 0           | case expression:    f xs = case xs of [] -> 0
Guards:            f [x] | x>0 = 'a'  | if expression:      f [x] = if x>0 then 'a' else ...
  • 在第一个示例中,为什么let-in范围是范围,但在哪里不是范围?
  • 有没有可能把第一个例子应用到哪里?
  • 有人能把这个应用到变量代表实际表达式的真实例子中吗?
  • 在使用每一种产品时,是否遵循一般的经验法则?
  • 更新

    对于后来出现在这条线上的人,我找到了最好的解释:"哈斯克尔的一个温和的介绍"。

    Let Expressions.

    Haskell's let expressions are useful
    whenever a nested set of bindings is
    required. As a simple example,
    consider:

    1
    2
    3
    let y   = a*b
        f x = (x+y)/y
    in f c + f d

    The set of bindings created by a let
    expression is mutually recursive, and
    pattern bindings are treated as lazy
    patterns (i.e. they carry an implicit
    ~). The only kind of declarations
    permitted are type signatures,
    function bindings, and pattern
    bindings.

    Where Clauses.

    Sometimes it is convenient to scope
    bindings over several guarded
    equations, which requires a where
    clause:

    1
    2
    3
    4
    f x y  |  y>z           =  ...
           |  y==z          =  ...
           |  y<z           =  ...
         where z = x*x

    Note that this cannot be done with a let expression, which only scopes over the expression which it encloses. A where clause is only allowed at the top level of a set of equations or case expression. The same properties and constraints on bindings in let expressions apply to those in where clauses. These two forms of nested scope seem very similar, but remember that a let expression is an expression, whereas a where clause is not -- it is part of the syntax of function declarations and case expressions.


    1:示例中的问题

    1
    2
    3
    f :: State s a
    f = State $ \x -> y
        where y = ... x ...

    是参数xwhere子句中的内容只能引用函数f的参数(没有)和外部范围中的内容。

    2:要在第一个示例中使用where,可以引入第二个命名函数以x为参数,如下:

    1
    2
    3
    f = State f'
    f' x = y
        where y = ... x ...

    或者像这样:

    1
    2
    3
    4
    f = State f'
        where
        f' x = y
            where y = ... x ...

    3:这里有一个完整的例子,没有...的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    module StateExample where

    data State a s = State (s -> (a, s))

    f1 :: State Int (Int, Int)
    f1 = State $ \state@(a, b) ->
        let
            hypot = a^2 + b^2
            result = (hypot, state)
        in result

    f2 :: State Int (Int, Int)
    f2 = State f
        where
        f state@(a, b) = result
            where
            hypot = a^2 + b^2
            result = (hypot, state)

    4:使用letwhere是一种口味问题。我使用let强调计算(通过将它移到前面),使用where强调程序流(通过将计算移到后面)。


    虽然ephemient所指出的关于保护的技术差异,但是在概念上,是否要将主公式放在前面,并在下面定义额外变量(where),还是要将所有内容都放在前面,并将公式放在下面(let)。每种样式都有不同的强调,在数学试卷、教科书等中都会用到。一般来说,如果变量不够直观,公式没有它们就没有意义,则应在上面定义变量;由于上下文或其名称而直观的变量应在下面定义。例如,在ephemient的hasmunary示例中,vowels的含义是显而易见的,因此不需要在其用法之上对其进行定义(忽略let由于保护而不起作用的事实)。


    法律:

    1
    main = print (1 + (let i = 10 in 2 * i + 1))

    不合法的:

    1
    main = print (1 + (2 * i + 1 where i = 10))

    法律:

    1
    2
    3
    4
    5
    hasVowel [] = False
    hasVowel (x:xs)
      | x `elem` vowels = True
      | otherwise = False
      where vowels ="AEIOUaeiou"

    不合法:(与ML不同)

    1
    2
    let vowels ="AEIOUaeiou"
    in hasVowel = ...


    我发现这个来自lyhfgg的例子很有用:

    1
    2
    ghci> 4 * (let a = 9 in a + 1) + 2  
    42

    let是一个表达式,因此您可以将let放在任何位置(!)表达式的位置。

    换句话说,在上面的示例中,不可能使用where简单地替换let(可能不使用更详细的case表达式与where组合)。