被误解的版本
所以,首先,我建议添加一个类型签名,这会对您有所帮助,因为编译器会为您提供更好的错误消息:
func :: [a -> b] -> a -> [b]
让我们编译它:
test.hs:4:9:
Couldn't match type ‘b’ with ‘[b]’
啊,所以在第 4 行(最初是第 3 行,因为我添加了类型签名),我们有类型 b 但我们需要一个列表!有道理,不是吗?所以那行应该是:
[(head xs) y] -- originally just `(head xs) y`
问题 1 已修复,让我们再次编译:
test.hs:6:9:
Couldn't match type ‘b’ with ‘a -> b’
‘b’ is a rigid type variable bound by
the type signature for func :: [a -> b] -> a -> [b] at test.hs:1:9
啊,所以我们想要输入b,但我们实际上找到了a -> b,它是一个函数。说得通!您忘记将值实际应用于函数。所以那条线应该是
(head xs) y : func (drop 1 xs) y -- instead of `(head xs) : func (drop 1 xs) y`
现在让我们再试一次:
Ok, modules loaded: Main.
已修复!
现在,让我们考虑如何在 Haskell 中更惯用地编写此代码:
我想,一种选择是模式匹配,它们比使用不安全的head 函数更好:
func' :: [a -> b] -> a -> [b]
func' fs x =
case fs of
f:[] -> [f x]
f:fs' -> f x : func' fs' x
但实际上我们可能会意识到,我们只是想在每个值上映射一些东西(在这种情况下是一个函数),所以这应该真的有效:
func' :: [a -> b] -> a -> [b]
func' xs y = map SOMETHING xs
而SOMETHING 是给定函数的东西,应该使用y 应用该函数。嗯,这很简单:\f -> f y 将给定一个函数f,将它与y 一起应用。所以
func' :: [a -> b] -> a -> [b]
func' xs y = map (\f -> f y) xs
做得很好。或无点风格:
func' :: [a -> b] -> a -> [b]
func' xs y = map (flip ($) y) xs
($) 函数具有签名(a -> b) -> a -> b,我们正是需要它,但前两个参数翻转(所以a -> (a -> b) -> b)可以通过flip ($) 实现。
我希望这是有道理的,否则只需添加评论。
希望版本正确
不幸的是,我误解了这个问题,所以让我们再试一次:对于 cmets,类型签名应该是 func :: [a -> a] -> a -> a。那就编译吧:
test2.hs:7:3:
Couldn't match expected type ‘a’ with actual type ‘[a]’
啊,很公平,在(head xs) y : func (drop 1 xs) y 行中,我们返回了一个列表,但实际上我们只需要一个值。所以这很容易解决,因为我们不想用原来的y 而是用first_function y 调用第二个函数。所以让我们把它改成
func (drop 1 xs) ((head xs) y)
然后它已经工作了:)。
让我们也试着让它更惯用一点:所以如果我们找到一个调用func [f1, f2, f3, f4] y,我们真正想要执行的是(f4 (f3 (f2 (f1 y))))。整个事情看起来已经很像折叠了。确实如此!
import Data.List (foldl')
func' :: [a -> a] -> a -> a
func' xs y = foldl' (\x f -> f x) y xs
或者再次
func' xs y = foldl' (flip ($)) y xs
如果您想知道为什么我使用foldl' 而不是foldl,请阅读此处why foldl is broken and shouldn't be used。