[英]Haskell - List of functions that operate on single value
我是Haskell的新手,正在做一些初学者练习。 我有一个函数,需要一个函数列表和一个值。 我需要通过列表中的每个函数从头开始发送该值,然后返回该值。 我已经考虑过递归和折叠...但是我认为某种递归方法是可行的。
我尝试了如下所示的递归方法,但是它达到了基本情况,返回了结果,我无法正确组合
func xs y =
if length xs == 1 then
(head xs) y
else
(head xs) y : func (drop 1 xs) y
我只是想不通! 任何帮助将是巨大的,谢谢!!!
因此,首先,我建议添加类型签名,这对您有帮助,因为编译器会为您提供更好的错误消息:
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 -> fy
将在给定函数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
a- a -> (a -> b) -> b
)可以实现通过flip ($)
。
我希望这是有道理的,否则只需添加一条评论即可。
不幸的是,我误解了这个问题,因此让我们再试一次:从注释开始,类型签名应为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
,请在这里阅读为什么foldl
坏了,不应该使用 。
如果我理解正确,则希望将功能列表和一个点作为输入,并将所有这些功能应用于该点。 例如,给定我们要拥有的三个功能的列表
func [f,g,h] x = f (g (h x))
上面的示例可以使用函数组合运算符重写为
func [f,g,h] x = (f . g . h) x
借助eta收缩,可以简化为
func [f,g,h] = f . g . h
这说明问题是:给定一个函数列表,返回它们的组成。
现在,考虑一个不同的问题:如果我们想解决(的推广)
anotherFunc [a,b,c] = a + b + c
我们将使用任一递归
anotherfunc [] = 0
anotherfunc (x:xs) = x + anotherfunc xs
或折
anotherFunc xs = foldr (+) 0 xs
请注意, anotherFunc
利用二进制运算(+)
及其中性元素0
(即x + 0 = 0 + x = x
的元素)。
对于最初的问题(组成函数列表),我们可以这样做:
func xs = foldr (.) id xs
实际上,身份函数id
是构成的中性元素。 我们甚至可以对上述内容进行eta合同
func = foldr (.) id
或者(如果需要)使用显式递归
func [] y = y
func (x:xs) y = x (func xs y)
-- or equivalently (x . func xs) y
另一种查看方式:
在意识到您想要的类型应该是[a -> a] -> a -> a
。 添加多余的括号以更改重点,我们得到[a -> a] -> (a -> a)
; 查看功能的另一种方法是,它获取功能列表并将其“压缩”为单个功能。
听起来与功能组合极为相似,确实如此! 您基本上是由函数列表而不是。组成的.
由两个运算符组成的运算符; 我们只想“保持所有功能的组合,直到只剩下一个”。 现在听起来很像是折叠:
func' :: [a -> a] -> (a -> a)
func' = foldr (.) _
但是,我们用什么来填补空白呢? GHC实际上告诉我们(至少对于GHC> = 7.8)!
foo.hs:3:19:
Found hole ‘_’ with type: a -> a
Where: ‘a’ is a rigid type variable bound by
the type signature for func' :: [a -> a] -> a -> a at foo.hs:2:10
Relevant bindings include
func' :: [a -> a] -> a -> a (bound at foo.hs:3:1)
In the second argument of ‘foldr’, namely ‘_’
In the expression: foldr (.) _
In an equation for ‘func'’: func' = foldr (.) _
集中在Found hole '_' with type: a -> a
一点。
如果您已经进行了一段时间,那么您会知道,对于任何a
,类型a -> a
的唯一合理函数是id
,因此这似乎是一个不错的选择。 从规范中考虑它也会导致id
; 折叠函数组成的“起始值”必须是要与所有其他函数组成的函数; 我们不希望它做任何事情,所以id
有意义。 如果我们的函数列表为空(例如func [] x
),这也是将应用于该值的函数-“将任何函数均不应用于一个值”听起来应该保持不变,因此id
适用。
所以1 :
func' :: [a -> a] -> (a -> a)
func' = foldr (.) id
为什么我要写func'
? 因为这不是你的func
; 您指定了该值应首先通过列表中的第一个函数,然后是第二个,最后是最后一个。 这意味着从func
获得f1 (f2 (f3 (f4 x)))
的效果的方法是编写func [f4, f3, f2, f1] x
; 当您写出完整的应用程序时,这些函数从左到左的顺序与列表中这些函数的从左到右的顺序相反。 .
以相反的顺序进行撰写,因此,通过折叠来构建我们的列表,也是如此.
: func' [f1, f2, f3, f4] x = f1 (f2 (f3 (f4 x)))
。
但这很容易解决。 我们可以通过reverse
传递函数列表,然后再将其馈入折叠! 这给了我们:
func :: [a -> a] -> (a -> a)
func = foldr (.) id . reverse
例:
λ func [("f1 . " ++), ("f2 . " ++), ("f3 . " ++)] "id $ x"
"f3 . f2 . f1 . id $ x"
1 “但是等等!” 我听到你在哭,“功能组合不是以id
为标识元素构成一个简单体吗?”。 从数学上是可以的,因此实际上这个功能“可能”就是: func' = mconcat
。 但是那个Monoid
实例实际上在Haskell中并不存在,因为它需要扩展才能写入,并且会与前奏中确实存在的另一个Monoid
实例重叠。 在Data.Monoid
有一个新类型的包装器Endo a
(包装Data.Monoid
a -> a
),它确实具有实例。
因此,您可以编写func' = appEndo . mconcat . map Endo
func' = appEndo . mconcat . map Endo
func' = appEndo . mconcat . map Endo
,但是在这一点上,我们通过利用monoid结构比折叠更“简单”是有争议的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.