我想了解let
绑定在Haskell中的工作原理(如果Haskell的实现方式不同,也许还有lambda演算?)
通过阅读Write you a Haskell,我知道这对于单个let
绑定是有效的。
let x = y in e == (\x -> e) y
这对我来说很有意义,因为它与绑定在lambda演算中的工作方式一致。我很困惑的地方是使用多个let
绑定,其中一个绑定可以引用上面的绑定。我将提供一个简单的示例。
原始代码:
let times x y = x * y
square x = times x x
in square 5
我对实现的猜测:
(\square times -> square 5) (\x -> times x x) (\x -> x * x)
这似乎不起作用,因为当lambda调用square时,times
未定义。但是,可以通过以下实现解决此问题:
(\square -> square 5) ((\times x -> times x x) (\x -> x * x))
至少在lambda演算中,这是实现此绑定的正确方法吗?
答案 0 :(得分:5)
可以使用作用域将times
/ square
的示例表示为lambda函数:
(\times -> (\square -> square 5)(\x -> times x x))(\x y -> x * y)
但是对于像递归或相互递归的let绑定来说,作用域还不够
let ones = 1 : ones in take 5 ones
let even n = n == 0 || odd (abs n - 1)
odd n = n /= 0 && even (abs n - 1)
in even 7
在lambda演算中,您可以将y-combinator定义为递归为
(\f -> (\x -> f (x x))(\x -> f (x x)))
这使您可以根据自身定义函数和值。由于键入约束but there are ways around that,这种表述不是合法的草草。
使用y-combinator,我们可以使用lambda演算来表达上述let-bindings:
(\ones -> take 5 ones)((\f -> (\x -> f (x x))(\x -> f (x x)))(\ones -> 1 : ones))
(\evenodd -> evenodd (\x y -> x) 7)((\f -> (\x -> f (x x))(\x -> f (x x)))(\evenodd c -> c (\n -> n == 0 || evenodd (\x y -> y) (abs n - 1)) (\n -> n /= 0 && evenodd (\x y -> x) (abs n - 1))))
答案 1 :(得分:3)
请注意,可以将多个let
绑定简化为单个绑定,从而定义一对(通常为元组)。例如。我们可以重写
let times x y = x * y
square x = times x x
in square 5
为
let times = \x y -> x * y
square = \x -> times x x
in square 5
然后
let (times, square) = (\x y -> x * y, \x -> times x x)
in square 5
然后,如果需要,
let pair = (\x y -> x * y, \x -> fst pair x x)
in snd pair 5
之后,我们可以应用通常的lambda演算转换。如果对定义最终是递归的(如上述情况),则需要定点组合器。
(\pair -> snd pair 5) (fix (\pair -> (\x y -> x * y, \x -> fst pair x x)))
请注意,此翻译不能与类型推断算法一起使用,后者以特殊方式处理let
,从而引入了多态性。不过,如果我们只关心程序的动态方面,那么这并不重要。
答案 2 :(得分:2)
我将回答自己的问题,以便为访问此问题的人提供有用的观点。
我们想用两个let绑定实现以下程序:
let times a b = a * b
square x = times x x
in square 5
首先,让我们将其简化为我们想要的内容:
square 5
足够简单。但是,square
在这种情况下是未定义的!好吧,我们可以使用我们的语言为我们提供的机制-lambda来绑定它。这给了我们(\ square -> square 5) (\x -> times x x)
。现在已经定义了square
,但是还没有定义它的堂兄times
...好吧,我们需要另一个lambda!我们的最终程序应如下所示:
(\times -> (\square -> square 5) (\x -> times x x)) (\a b -> a * b)
请注意,(\times -> ...)
完全包围了我们的最后一步,因此times
将在绑定范围内。这与@rampion给出的答案一致,并减少如下:
(\times -> (\square -> square 5) (\x -> times x x)) (\a b -> a * b) =>
(\square -> square 5) (\x -> (\a b -> a * b) x x) =>
(\square -> square 5) (\x -> (\b -> x * b) x) =>
(\square -> square 5) (\x -> x * x) =>
(\x -> x * x) 5 =>
5 * 5 =>
25
如果square
函数不依赖于times
,我们可以很容易地编写(\times square -> ....
。依赖关系意味着我们必须嵌套这两个环境,一个环境包含times
,另一个环境可以使用其定义。
感谢您的所有帮助! lambda演算的简单性和功能让我震惊。