我是Haskell的新手,正在尝试编写一个函数,可以将一个整数与列表中的每个元素相乘。
例如:
mult 2 [2,4,6]
返回:
[4,8,12]
将问题拆解成概念步骤:
你想对列表的每个元素执行某个操作,这是由map
函数提供的一般操作。使用它,你可以忽略列表,只考虑需要对单个元素执行的操作。
你想将两个数字相乘。在任何一个函数使用中,其中一个数字将是常量,因此我们可以将其作为函数的参数给定一个名称:mult x = ...
。现在我们可以将x
视为常量,只关注另一个数字。
另一个数字不是常量,因此你需要一个函数,而不仅仅是一个简单的表达式。Haskell 提供了“操作符部分应用”来使用像(*)
这样的中缀操作符,因此使用x
我们得到 (x *)
。
回溯最后几步,你现在给x
赋予了一个名称,并创建了一个函数,然后将其传递给map
。
mult x = map (x *)
...现在你已经完成了,如果你想的话。但对于初学者来说,将列表作为显式参数可能更清晰:
mult x ys = map (x *) ys
两种形式都是做同样的事情。
我最初的回答有点轻描淡写,所以让我们再试一次。我的意图是双重的:一方面为了弥补我对新手写了一个傲慢的、睡眠不足的回答,:-),另一方面是尝试暗示无点风格的观点:
mult_l = map . (*)
我该代码做什么?
将其视为流水线方式会很有帮助:
这段代码将(*)
传入到map
中。
(*)
的类型是什么?它是Num a => a -> a -> a
。这意味着它做了什么?它接受一个数字(称之为x
),并给你另一个函数,这个函数将——如果给定另一个数字(称之为y
)——计算x
“乘以”y
。(我把“乘以”放在括号里,因为Num
是一个类型类...)
现在你要将(*)
与map
组合起来。 map
是什么?好吧,让我们看看它的类型:(a -> b) -> [a] -> [b]
。所以现在,map
接受一个函数作为参数,并将该函数应用于列表的每个成员。
(f . g) x = f (g x)
。mult_l 2
真的很
(map . (*)) 2
这实际上只是:真的
map (* 2)
map
需要两个参数,一个是函数(告诉它“你想让我做什么?”),另一个是列表(告诉它“你想对哪个进行操作?”)。map
然后逐个将列表中的每个项,按点应用于该函数的元素。map (* 2) [1,2,3]
map . (*)
的类型是Num a => a -> [a] -> [a]
,因为它接收一个数字x
,然后将高阶函数\x -> (* x)
传递给map
。map
?我会给你一个提示:
map
的递归定义,以及相关的归纳原理。
你可能会想知道的另一件事是:为什么我必须写map . (*)
而不是只写map (*)
。如果你思考一段时间,你可能会对无点风格的概念有更深入的了解。
由于没有人谈论列表推导,因此只是为了介绍另一种方法。
mult x ys = [i*x | i <- ys]