Haskell:在哪里与让


117

我是Haskell的新手,我对Where vs. Let感到非常困惑。它们似乎都提供了相似的目的。我已阅读之间的一些比较哪里Let但是我很难辨别何时使用它们。有人可以提供一些背景信息,也可以提供一些示例来说明何时使用另一种方法吗?

在哪里与让

where子句只能在一个函数定义的电平来定义。通常,这与let定义范围相同。唯一的区别是使用防护装置的时间。该where条款的范围涵盖了所有保护措施。相反,let表达式的范围仅是当前函数子句和保护符(如果有)。

Haskell备忘单

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

Let的优点

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

Control.Monad.State

将不起作用,因为where是指与f =匹配的模式,其中范围内没有x。相反,如果您从let开始,那么您将不会有麻烦。

Haskell Wiki关于Let的优势

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

哪里的优势

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表达式是表达性的。除了风格之外,他们如何表现不同?

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. 是否可以申请 在哪里第一个例子?
  3. 有人可以将其应用于变量表示实际表达式的真实示例吗?
  4. 有什么通用的经验法则可以遵循?

更新资料

对于后来由该主题引起的读者,我在这里找到了最好的解释:“ 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表达式来完成,let表达式只能在其所包围的表达式范围内。where子句仅在一组方程式或case表达式的顶层被允许。let表达式中对绑定的相同属性和约束适用于where子句中的属性和约束。这两种形式的嵌套作用域看起来非常相似,但请记住,let表达式是一个表达式,而where子句不是-它是函数声明和case表达式的语法的一部分。


9
第一次学习Haskell letwhere何时开始学习之间的区别让我感到困惑。我认为最好的理解方法是认识到两者之间的差异很小,因此无需担心。通过非常简单的机械转换where给出 的含义let。请参阅haskell.org/onlinereport/decls.html#sect4.4.3.2确实, 这种转换仅出于符号上的方便而存在。
汤姆·埃利斯

我通常根据要首先定义的内容使用一个或另一个。例如,人们经常使用函数,然后在哪里定义它们。如果需要一种命令式功能,则使用Let。
PyRulez 2013年

@汤姆·埃利斯(Tom Ellis),汤姆(Tom),我试图理解您所指的链接,但对我来说太难了,您能解释一下这种简单的转变为凡人吗?
jhegedus

1
@jhegedus:f = body where x = xbody; y = ybody ...表示f = let x = xbody; y = ybody ... in body
Tom Ellis,

谢谢汤姆!它能反过来吗?是否可以将let表达式转换为case .... of ... where某种表达式?我对此不确定。
jhegedus 2015年

Answers:


39

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:何时使用let还是where味道问题。我let经常强调计算(通过将其移至最前面),并where强调程序流程(通过将计算向后移)。


2
“ where子句中的内容只能引用函数f的参数(不存在),而只能引用外部作用域中的内容。” -确实可以帮助我澄清一下。

28

短暂性指出的守卫方面存在技术差异,但在概念上是否要在主公式中使用下面定义的额外变量(where)还是在先定义所有内容并放入公式也存在概念差异在(let)下。每种风格都有不同的侧重点,您会在数学论文,教科书等中看到这两种风格。通常,应该在上面定义足够直观的变量,如果没有它们,则公式将变得毫无意义。由于上下文或名称而直观的变量应在下面定义。例如,在ephemient的hasVowel示例中,的含义vowels很明显,因此不需要在其用法上方定义(忽略let由于守卫而无法工作的事实)。


1
这提供了一个很好的经验法则。您能否详细说明为什么让范围与何处不同?

因为Haskell语法是这样说的。抱歉,没有一个好的答案。如果将顶级名称的定义塞在“ let”下,则很难理解,因此是不允许的。
gdj 2010年

13

法律:

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 = ...

13
您能否解释以下示例为何有效而另一个示例无效的原因?

2
对于那些不知道,这是合法的:hasVowel = let^M vowels = "AEIOUaeiou"^M in ...^M就是换行)
托马斯Eding

5

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

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

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

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


3

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

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

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


这是唯一可以帮助我理解差异的答案。虽然技术答案可能对更有经验的Haskell-er有用,但这对于像我这样的初学者来说足够简洁!+1
Zac G
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.