Lambda演算中的Haskell`let`绑定

时间:2019-01-06 05:08:36

标签: haskell recursion let lambda-calculus letrec

我想了解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演算中,这是实现此绑定的正确方法吗?

3 个答案:

答案 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演算的简单性和功能让我震惊。