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

时间:2015-05-09 20:55:19

标签: list function haskell recursion

我是Haskell的新手,正在做一些我发现的初学者练习。我有一个函数,它接受函数列表和值。我需要通过列表中的每个函数发送该值,从头开始,然后返回该值。我考虑了递归和折叠...但我认为某种递归方法将是要走的路。

我尝试了一种类似于下面的递归方法,但它得到了基本情况,返回,我无法正确组合

func xs y =
 if length xs == 1 then
  (head xs) y
 else
  (head xs) y : func (drop 1 xs) y

我无法弄明白! 任何帮助都会很棒,谢谢!

3 个答案:

答案 0 :(得分:2)

误解版

所以,首先,我建议添加一个类型签名,这可以帮助您,因为编译器会为您提供更好的错误消息:

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 ($)实现。

我希望这是有道理的,否则只需添加评论。

希望更正版本

我遗憾地误解了这个问题,让我们再试一次:截至评论时,类型签名应为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

答案 1 :(得分:0)

如果我已正确理解,您希望将功能列表和点作为输入,并将所有这些功能应用于该点。例如,给出我们想要的三个函数的列表

func [f,g,h] x = f (g (h x))

上面的例子可以使用函数合成运算符重写为

func [f,g,h] x = (f . g . h) x

,由于eta-contraction,可以简化为

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是构成的中性元素。 我们甚至可以将上述内容合同到

func = foldr (.) id

或者,如果愿意,可以使用显式递归

func []     y = y
func (x:xs) y = x (func xs y)
  -- or equivalently (x . func xs) y

答案 2 :(得分:0)

另一种看待这种情况的方式:

在意识到您想要的类型应该是[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}符合条件。

所以 1

func' :: [a -> a] -> (a -> a)
func' = foldr (.) id

为什么要写func'?因为这不是你的func;您指定该值应首先通过列表中的第一个函数,然后是第二个函数,最后是最后一个函数。这意味着从f1 (f2 (f3 (f4 x)))中获取func效果的方法是写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中有一个newtype包装器Endo a(包装a -> a),它有实例。

所以你可以写func' = appEndo . mconcat . map Endo,但是在那一点上,通过利用幺半群结构我们比折叠更“简单”是值得商榷的。