我猜你得到的错误是因为尝试简单地将foldr
切换为foldl'
:
myConcat xs ys = foldl' (:) ys xs
这会产生错误(使用我的 Hugs REPL):
ERROR - Type error in application
*** Expression : foldl' (:) xs ys
*** Term : (:)
*** Type : a -> [a] -> [a]
*** Does not match : [a] -> a -> [a]
注意最后两行(提供的类型和期望的类型),[a]
和a
的位置是相反的。这意味着我们需要一个函数,类似于(:)
,但它接受的参数顺序相反。
Haskell有一个为我们完成此操作的函数:flip
函数。基本上,flip
等同于
flip :: (a -> b -> c) -> (b -> a -> c)
flip f y x = f x y
也就是说,flip
接收一个二元函数作为参数,并返回另一个将原始参数反转("翻转")的二元函数。因此,虽然 (:)
的类型为 a -> [a] -> [a]
,但我们可以看到 flip (:)
的类型为 [a] -> a -> [a]
,使它成为 foldl'
参数的完美选择。
使用 flip
,我们现在有以下代码:
myConcat xs ys = foldl' (flip (:)) ys xs
这是因为 foldl'
具有类型 (a -> b -> c) -> a -> [b] -> c
。
如果我们用参数 [1..5]
和 [6..10]
运行它,我们会得到一个结果 [5,4,3,2,1,6,7,8,9,10]
,几乎是我们想要的。唯一的问题是第一个列表在结果中反向出现了。将一个简单的调用 reverse
添加进去就可以得到我们最终的 myConcat
定义:
myConcat xs ys = foldl' (flip (:)) ys (reverse xs)
审视这个过程,展示了在编写Haskell代码时经常出现的美好事物之一:当遇到问题时,您可以一步一步地解决它(小问题)。当您已经有一个工作实现,并且只是尝试编写另一个实现时,这一点尤其重要。需要注意的重要一点是,如果您更改实现的某个部分(在本例中,更改
foldr
为
foldl'
),则许多其他所需的更改将简单地落在类型定义中。剩下的几个问题是正确性问题,可以通过运行测试用例或查看使用的函数的确切性质来轻松找到。
PS:任何熟悉Haskell的人都可以更新一下最后一行代码,如果愿意的话。虽然它并不可怕,但我认为它并不太好看。不幸的是,我还不太擅长Haskell。