Haskell - 功能中的冗余部分

时间:2017-03-18 10:54:23

标签: function haskell functional-programming

我有一个错综复杂的Haskell函数:

j :: [Int]
j = ((\f x -> map x) (\y -> 3 + y) (\z -> 2*z)) [1,2,3]

我的猜测是,(\y -> 3 + y) (\z -> 2*z)将更改为(\w -> 3 + 2*w),并与f一起打印出列表[5,7,9]

当我用ghci检查时,我得到了[2,4,6]

问题(\y -> 3 + y)这是一个多余的表达式吗?如果是这样,为什么?

2 个答案:

答案 0 :(得分:3)

这是因为它反之亦然,即您首先将(\f x -> map x)应用于(\y -> 3 + y)。但是

(\f x -> map x) something g

变为

let f = something; x = g in map x

这最终是

map g

所以something没有出现在结果表达式中,因为从箭头的任何位置都没有提到f

如果我理解正确,你想要

(\f x -> map (f . x))

补充说明:从你的论证来看,你似乎还没有掌握β降低的效果。

例如,即使表达式会像你想象的那样应用:

(\y -> 3 + y) (\z -> 2*z)

结果将是

3 + (\z -> 2*z)

不,这是否有意义是值得怀疑的,然而,它是β减少的工作方式:它从箭头中给出了正确的部分,其中每个参数的出现都被实际参数替换。

答案 1 :(得分:3)

我认为你在功能设计的某个地方出了问题,但我会给你解释这里发生的机制。如果您仍然不理解,请留意详细说明。

快速回答:评估表达式

j = ((\f x -> map x) (\y -> 3 + y) (\z -> 2*z)) [1,2,3]
  = ((\x -> map x) (\z -> 2*z)) [1,2,3]
  = (map (\z -> 2*z)) [1,2,3] -- f := (\y -> 3 + y), x := (\z -> 2*z)
  = [2,4,6]

您可能会看到,当Haskell评估(\f x -> map x) (\y -> 3 + y)时,由于函数应用程序从左到右进行评估,因此会进行替换f := (\y -> 3 + y),但f不会出现在函数的任何地方,它只是(\x -> map x)

详细答案:左(和右)关联运算符

在Haskell中,我们说函数应用程序是左关联的。这就是说,当我们编写一个函数应用程序时,它的评估如下:

function arg1 arg2 arg3 = ((function arg1) arg2) arg3

无论参数的类型是什么。 这称为左关联,因为括号总是在左侧。

您似乎希望您的表达式表现得像这样:

  (\f x -> map x) (\y -> 3 + y) (\z -> 2*z)
= (\f x -> map x) ((\y -> 3 + y) (\z -> 2*z)) -- Incorrect

然而,正如我们所看到的,函数关联左,而不是你想象的那样,所以你的表达式看起来像Haskell:

  (\f x -> map x) (\y -> 3 + y) (\z -> 2*z)
= ((\f x -> map x) (\y -> 3 + y)) (\z -> 2*z)
= (\x -> map x) (\z -> 2*z)                   -- Correct!

请注意,我为评估而订的括号位于左侧,而不是右侧。

但是,Haskell定义了一个非常有用的权限关联函数应用程序运算符,即($) :: (a -> b) -> a -> b。您可以像这样重写您的表达式,以获得您期望的结果。

  (\f x -> map x) $ (\y -> 3 + y) (\z -> 2*z)
= (\f x -> map x) $ ((\y -> 3 + y) (\z -> 2*z)) -- Correct, though nonsensical.

但是,正如您将注意到的那样,(\f x -> map x)中仍未引用f,因此完全被忽略。我不确定你实现这个功能的目标是什么

进一步的问题:Lambda表达式&组合物

我意识到另一个问题可能是你对lambda表达式的理解。考虑这个功能:

f x = x + 2

我们可以将其重写为lambda表达式,如下所示:

f = \x -> x+2

但是,如果我们有两个参数怎么办?这就是我们的工作:

g x y = x + y

g = \x -> (\y -> x+y)

Haskell模拟多个参数的方式称为currying。你可以看到函数实际返回另一个函数,然后返回一个函数,它最终返回它应该具有的函数。但是,这种表示法很长且很麻烦,因此Haskell提供了另一种选择:

g = \x y -> x + y

这可能似乎是不同的,但实际上它是与以前完全相同的表达式的语法糖。现在,看看你的第一个lambda表达式:

\f x -> map x = \f -> (\x -> map x)

你可以看到函数中根本没有引用参数f,所以如果我对它应用了某些东西:

  (\f x -> map x) foo
= (\f -> (\x -> map x)) foo
= \x -> map x

这就是为什么你的(\y -> 3 + y)被忽略了;你还没有在你的功能中使用它。

此外,您希望表达式(\y -> 3 + y) (\z -> 2*z)评估为\w -> 3 + 2*w。这不是真的。左边的lambda用y替换每次出现的(\z -> 2*z),结果是完全没有意义的3 + (\z -> 2*z)。如何为数字添加函数?!

你正在寻找的是composition.我们在Haskell中有一个运算符,即(.) :: (b -> c) -> (a -> b) -> (a -> c),可以帮助你解决这个问题。它需要左侧和右侧的功能,并创建一个新功能,将功能“管道”到彼此。也就是说:

(\y -> 3 + y) . (\z -> 2*z) = \w -> 3 + 2*w

您正在寻找的是什么。

结论:更正表达

我认为您正在寻找的表达方式是:

j = ( (\x -> map x) $ (\y -> 3 + y) . (\z -> 2*z) ) [1,2,3]

这相当于说:

j = map (\w -> 3 + 2*w) [1,2,3]

由于您似乎在使用Haskell的更基本部分时遇到了很多麻烦,我建议使用典型的初学者书籍Learn You a Haskell for Great Good.