Haskell:Where vs. Let

109 haskell where keyword let

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

哪里与让

where子句只能在一个函数定义的电平来定义.通常,这与let定义的范围相同.唯一的区别是使用警卫时.该where条款的范围扩展到所有警卫.相反,let表达式的范围只是当前的函数子句和guard,如果有的话.

哈斯克尔作弊表

哈斯克尔维基是非常详细,并提供各种案件,但它使用的假设的例子.我觉得它的解释对初学者来说太简短了.

让我们的优势:

f :: State s a
f = State $ \x -> y
   where y = ... x ...
Run Code Online (Sandbox Code Playgroud)

Control.Monad.State

将无法工作,因为where指的是模式匹配f =,其中没有x在范围内.相比之下,如果你开始使用let,那么你就不会遇到麻烦了.

关于Let的优点的Haskell Wiki

f :: State s a
f = State $ \x ->
   let y = ... x ...
   in  y
Run Code Online (Sandbox Code Playgroud)

其中的优点:

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)
Run Code Online (Sandbox Code Playgroud)

宣言与表达

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

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 ...
Run Code Online (Sandbox Code Playgroud)
  1. 在第一个例子,为什么是咱们的范围,但如果不是呢?
  2. 是否有可能应用在哪里第一个例子?
  3. 有些人可以将此应用于变量代表实际表达式的实例吗?
  4. 是否有一般的经验法则可以遵循何时使用?

更新

对于那些后来通过这个帖子的人,我找到了最好的解释:" Haskell的温和介绍 ".

表达.

只要需要嵌套的绑定集,Haskell的let表达式就很有用.举个简单的例子,考虑一下:

let y   = a*b
    f x = (x+y)/y
in f c + f d
Run Code Online (Sandbox Code Playgroud)

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

条款.

有时,将绑定范围扩展到几个保护方程是很方便的,这需要where子句:

f x y  |  y>z           =  ...
       |  y==z          =  ...
       |  y<z           =  ...
     where z = x*x
Run Code Online (Sandbox Code Playgroud)

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

ant*_*kos 37

1:示例中的问题

f :: State s a
f = State $ \x -> y
    where y = ... x ...
Run Code Online (Sandbox Code Playgroud)

是参数x.在物联网where子句只能指函数的参数f(没有),事情在外部范围.

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

f = State f'
f' x = y
    where y = ... x ...
Run Code Online (Sandbox Code Playgroud)

或者像这样:

f = State f'
    where
    f' x = y
        where y = ... x ...
Run Code Online (Sandbox Code Playgroud)

3:这是一个没有...'s 的完整例子:

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)
Run Code Online (Sandbox Code Playgroud)

4:何时使用let或是where品味问题.我let用来强调计算(通过将其移动到前面)并where强调程序流程(通过将计算移动到后面).

  • "where子句中的东西只能引用函数f的参数(没有)和外部作用域中的东西." - 这真的有助于我澄清它. (2认同)

gdj*_*gdj 26

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

  • 这提供了一个很好的经验法则。您能否详细说明为什么 let 的范围似乎与 where 不同? (2认同)

eph*_*ent 12

法律:

main = print (1 + (let i = 10 in 2 * i + 1))
Run Code Online (Sandbox Code Playgroud)

不合法:

main = print (1 + (2 * i + 1 where i = 10))
Run Code Online (Sandbox Code Playgroud)

法律:

hasVowel [] = False
hasVowel (x:xs)
  | x `elem` vowels = True
  | otherwise = False
  where vowels = "AEIOUaeiou"
Run Code Online (Sandbox Code Playgroud)

不合法:(与ML不同)

let vowels = "AEIOUaeiou"
in hasVowel = ...
Run Code Online (Sandbox Code Playgroud)

  • 你能解释为什么下面的例子是有效的而另一个不是吗? (12认同)
  • 对于那些不知道的人,这是合法的:`hasVowel = let ^ M元音=“ AEIOUaeiou” ^ M in ...`(`^ M`是换行符) (2认同)

Bor*_*per 7

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

LHYFGG有一个相关的章节 - 如果你还没有,你应该阅读它,但本质上:

  • where只是一个语法结构(不是),仅在函数定义时有用。
  • 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
Run Code Online (Sandbox Code Playgroud)

我们在列表推导式中包含一个let ,就像我们在谓词中一样,只是它不过滤列表,它只绑定到名称。列表推导式中的 let 中定义的名称对输出函数( 之前的部分|)以及绑定之后的所有谓词和部分可见。所以我们可以让我们的函数只返回大于等于 25 的人的 BMI:

  • 这是真正帮助我理解差异的唯一答案。虽然技术答案可能对更有经验的 Haskell-er 有用,但对于像我这样的初学者来说,这已经足够简洁了!+1 (3认同)

jhe*_*dus 5

我发现LYHFGG的这个示例很有帮助:

ghci> 4 * (let a = 9 in a + 1) + 2  
42  
Run Code Online (Sandbox Code Playgroud)

let是一个表达式,因此您可以将表达式放到let 任何地方(!)

换句话说,在它上面的例子中是可能使用where简单地替换let(不可能使用一些更详细的case表达联合where)。