Haskell:Where vs. Let

时间:2010-12-06 00:53:44

标签: haskell keyword where let

我是Haskell的新手,我对哪里感到困惑。它们似乎都提供了类似的目的。我已经阅读了哪里之间的一些比较,但我无法辨别何时使用每一项。有人可以提供一些上下文或者一些例子来说明何时使用其中一个?

  

Where vs. Let

     

where子句只能在函数定义的级别定义。通常,这与let定义的范围相同。 唯一的区别是使用防护装置where子句的范围扩展到所有警卫。相反,let表达式的范围只是当前的函数子句和保护(如果有的话)。

Haskell Cheat Sheet

Haskell Wiki非常详细并提供了各种案例,但它使用了假设的例子。我觉得它的解释对初学者来说太短暂了。

的优势
f :: State s a
f = State $ \x -> y
   where y = ... x ...

Control.Monad.State

  

不起作用,因为在哪里指的是   模式匹配f =,其中没有x   在范围内。相反,如果你有   从let开始,然后你不会   有麻烦。

Haskell Wiki on Advantages of Let

f :: State s a
f = State $ \x ->
   let y = ... x ...
   in  y

Where 的优势:

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)

Declaration vs. Expression

Haskell wiki提到 Where 子句是声明性的,而表达式具有表现力。除了风格,它们的表现如何不同?

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 ...
  1. 在第一个例子中,为什么在范围内,但哪里不是?
  2. 是否可以将 Where 应用于第一个示例?
  3. 有些人可以将此应用于变量代表实际表达式的实例吗?
  4. 使用每种方法时是否遵循一般经验法则?

  5. 更新

    对于那些后来通过这个帖子的人,我找到了最好的解释:“A Gentle Introduction to Haskell”。

      

    让表达。

         

    Haskell的let表达式非常有用   每当一组嵌套的绑定时   需要。举个简单的例子,   考虑:

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

    let创建的绑定集   表达式是相互递归的,并且   模式绑定被视为惰性   模式(即它们带有隐含的   〜)。唯一的声明   允许的是类型签名,   功能绑定和模式   绑定。

         

    Where子句。

         

    有时候范围很方便   几个守卫的绑定   方程式,需要在哪里   子句:

    f x y  |  y>z           =  ...
           |  y==z          =  ...
           |  y<z           =  ...
         where z = x*x
    
         

    请注意,使用let表达式无法做到这一点,该表达式仅覆盖它所包含的表达式。 where子句仅允许在一组方程或case表达式的顶层。 let表达式中绑定的相同属性和约束适用于where子句中的绑定。这两种形式的嵌套范围看起来非常相似,但请记住let表达式是一个表达式,而where子句则不是 - 它是函数声明和case表达式语法的一部分。

5 个答案:

答案 0 :(得分:34)

1:示例中的问题

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

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

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

f = State f'
f' x = y
    where y = ... x ...

或者像这样:

f = State f'
    where
    f' x = y
        where y = ... x ...

3:这是一个没有...的完整示例:

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来强调程序流(通过将计算移动到后面)。

答案 1 :(得分:23)

虽然ephemient指出的守卫存在技术差异,但是你是否想要将主要公式预先设置为下面定义的额外变量(where)或者你是否想要预先定义所有内容并将公式放在下面(let)。每种风格都有不同的重点,你会看到它们都用在数学论文,教科书等中。一般来说,如果没有它们,那么公式就没那么有意义的变量应该在上面定义;由于上下文或其名称而直观的变量应在下面定义。例如,在ephemient的hasVowel示例中,vowels的含义是显而易见的,因此不需要在其使用之上定义(忽略let因保护而无效的事实。)

答案 2 :(得分:11)

法律:

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

不合法:

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

法律:

hasVowel [] = False
hasVowel (x:xs)
  | x `elem` vowels = True
  | otherwise = False
  where vowels = "AEIOUaeiou"

不合法:(与ML不同)

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

答案 3 :(得分:3)

我在LYHFGG帮助中找到了这个例子:

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

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

换句话说,在上面的例子中,使用where来简单地替换let <(可能没有使用更详细的case表达式)结合where)。

答案 4 :(得分:1)

遗憾的是,这里的大多数答案对于初学者来说太技术性了。

LHYFGG上有相关的章节-如果您尚未阅读,则应阅读,但本质上是:

  • where 只是一种语法构造(不是 sugar ),仅在函数定义中有用。
  • >
  • let ... in 表达式本身,因此您可以在可以放置表达式的任何地方使用它们。作为一个表达式本身,它不能用于约束守卫人员。

最后,您也可以在列表推导中使用 let

calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
-- w: width
-- h: height
  

我们在列表理解中包含一个 let ,就像我们的谓词一样,只是它不过滤列表,仅绑定到名称。列表理解内的let中定义的名称对输出函数(|之前的部分)以及绑定之后的所有谓词和部分可见。因此,我们可以使函数仅返回> = 25的人的BMI: