折叠一个函数列表?

4

我正在尝试将列表中任意数量的数学函数组合在一起。

我编写了一个compose函数,它将它们链接在一起:

chain :: (c -> d) -> (a -> b -> c) -> a -> b -> d
g `chain` f = \a b -> g (f a b)

如果要做这样的事情,

let b = (*) `chain` (+) `chain` (-)

创建一个函数,它接受 a b c d 参数,并执行 ((ab) + c) × d 的操作。
> b 2 3 4 5
> 15

我的问题是,我希望能够根据任意列表将一组这些链接在一起:
[(*),(+),(-)] 将导致 (*) `chain` (+) `chain` (-)
[(*),(+),(-),(*)] 将导致 (*) `chain` (+) `chain` (-) `chain` (*)
这是否可能?我尝试使用折叠,但无法使其工作,因为每次应用元素后累加器的类型都会改变。
例如,三个函数链接在一起的类型为:
b :: Integer -> Integer -> Integer -> Integer -> Integer

但是对于四个而言,它是这样的:
b :: Integer -> Integer -> Integer -> Integer -> Integer -> Integer

如果有人能指点我正确的方向或知道解决方案,那就太好了!谢谢。
编辑:
我的最终目标是能够做到这样:
getZipList $ pure (function composing * and +) <*> ZipList [1,2,3] <*> ZipList [4,5,6] 
    <*> ZipList [7,8,9]

会导致:
[(1+4)*7, (2+5)*8, (3+6)*9]

4
Sitenote: 你的 "chain" 函数相当于 Data.Composition 中的 ".:" 运算符。 - Stephan Kulla
pure(函数组合*和+)<*> [1,2,3] <*> [4,5,6] <*> [7,8,9] 不会得出 [(1+4)*7, (2+5)*8, (3+6)*9] 而是得出(x+y)*z的所有可能组合,其中x属于[1,2,3],y属于[4,5,6],z属于[7,8,9]。为此,您应该使用zipList构造函数: http://hackage.haskell.org/package/base-4.6.0.1/docs/Control-Applicative.html。 - Luc DUZAN
@tampis 顺便提一下:这实际上只是 (.).(.) - Cubic
@LucDUZAN 你说得对,我忘记在这里做注释了。谢谢! - Chris
2个回答

9

好的,正如你自己所指出的那样,这似乎不可能,因为不同的列表长度(对类型系统不可见)会导致不同的类型。唯一“稳定”的方法是使用某种编译时列表;有各种各样的列表可用,但没有一个真正稳定支持且易于使用。

最简单的替代方案是将参数也作为列表传递。即:

ifxChain :: [a->a->a] -> [a] -> a

那个很容易实现:基本上是使用zip为每个函数提供第二个参数,省略第一个参数。然后通过单参数函数的结果列表折叠该第一个参数。
ifxChain fs (p0:ps) = foldl' (flip ($)) p0 $ zipWith flip fs ps

您可能希望添加对长度不匹配的列表的处理,这应该是可以相当安全的。
如果你坚持要使用实际的可变函数参数(我认为这不是好的选择!),那么你需要进行一些类型类的技巧。这种可变参数函数最为人所知的是printf
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances     #-}

class ChainedFunc f t where
  chainIfxs :: [t->t->t] -> t -> f
  chainIfxs fs i = chainIfxPre i id $ reverse fs
  chainIfxPre :: t -> (t->t) -> [t->t->t] -> f

instance ChainedFunc t t where
  chainIfxPre i f [] = f i

instance (ChainedFunc f t) => ChainedFunc (t->f) t where
  chainIfxPre i fin (f:fs) x0 = chainIfxPre i (flip f x0 . fin) fs

显然,这在很多方面都不太好。但是,嗯,它起作用...啊...

Main> chainIfxs [(*),(+),(-)] (2 :: Int) (3 :: Int) (4 :: Int) (5 :: Int) :: Int
15


2

这个想法能帮助你开发一个通用函数吗?

Prelude> :m +Data.List

Prelude Data.List> let fs = [(-),(+),(*)]

Prelude Data.List> foldl' (\accum (i,f) -> accum `f` i) 2 (zip [3..5] fs)
15

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接