繁体   English   中英

Haskell-对单个值进行操作的函数列表

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM