我有一个错综复杂的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)
这是一个多余的表达式吗?如果是这样,为什么?
答案 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表达式的理解。考虑这个功能:
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.