制作部分应用的功能列表(优雅或惯用)

时间:2014-06-16 04:35:10

标签: haskell currying

我几乎可以通过我的Haskell问题绊倒我的方式,但我没有找到更好的解决方案来解决我的问题。

假设我有一个函数f,它有5个参数,我想创建一个部分应用的函数列表,它们应用了前3个参数,但在列表的每个元素中都不同。

例如,假设f :: Num a => a -> a -> a -> b -> b -> c我希望最终得到[b -> b -> c]作为结果的类型。其中一项功能可能是f 1 3 5,另一项可能是f 6 4 2

有一个参数,我可以做一些像

这样的事情
map f [1..4] 

获取f 1f 2等,并且我可以使用2个参数

map (uncurry f) $ zip [1..3] [6..8].

现在我可以做3个args

map (uncurry $ uncurry f) $ zip (zip [1..3] [6..8]) [3..5]

但是这种速度非常快。是否有更优雅(或惯用)的方式(除了使我自己的“uncurry3”功能与zip3配对)?我总是遇到一个优雅的Haskell解决方案,这看起来非常笨拙。

很抱歉,如果这是新手问题或之前已经回答过。感谢。

2 个答案:

答案 0 :(得分:12)

您可以使用zipWith缩短2参数代码:

zipWith f [1..3] [6..8]

方便的是,标准库中实际上有一个zipWith3(以及最多7个)。

还有parallel list comprehensions -XParallelListComp,似乎可以达到任意数字:

[f a b c | a <- [1..3] | b <- [6..8] | c <- [3..5]]

答案 1 :(得分:9)

这实际上是一种为列表定义Applicative实例的方法。

回想一下,Applicative的定义围绕(<*>)

的定义
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

如果你专注于[],你可以得到:

(<*>) :: [a -> b] -> [a] -> [b]

也许这开始看起来像你可以做到这一点的方式?您有一个函数列表,您可以将它们应用于值列表。也许我们可以使(<*>)以某种方式工作,以便将函数列表应用于值列表,如zip:

fs <*> xs = zipWith ($) fs xs

召回($),函数应用程序运算符:

($) :: (a -> b) -> a -> b
f $ x = f x

所以zipWith“拉链”一个函数列表和一个值列表,并返回将每个函数应用到相应值的结果。

我想你应该可以从这里拿走它。让我们将两个列表加在一起:

(fmap (+) [1,2,3]) <*> [4,5,6]

变成

[(1+), (2+), (3+)] <*> [4,5,6]

变成

[1+4, 2+5, 3+6]

[5, 7, 9]

三个参数函数怎么样?

f x y z = x * y + z

((fmap f [1,2,3]) <*> [4,5,6]) <*> [7,8,9]
([(\y z -> 1*y+z), (\y z > 2*y+z), (\y z -> 3*y+z)] <*> [4,5,6]) <*> [7,8,9]
[(4+), (10+), (18+)] <*> [7,8,9]
[11, 18, 27]

纯!

通过仅使用另一个(<*>)来看,你可以将它扩展到任意性功能并不难。

此外,我们可以为fmap定义一个方便的别名并使用正确的固定名称并将其命名为(<$>),并定义(<*>)以获得不需要括号的正确固定,我们可以做类似

的事情
f <$> [1,2,3] <*> [4,5,6] <*> [7,8,9]

哪个很整洁,对吧?现在,您基本上可以使用任意数量的参数执行zipWithN ... zipWith

不幸的是[]的默认Applicative实例没有这种行为;它的行为方式与Monad实例一致。因此,为了解决这个问题,我们通常使用newtype包装器让我们为同一类型定义不同的实例。在标准库中,在Control.Applicative中,newtype包装器为ZipList

data ZipList a = ZipList { getZipList :: [a] }

instance Applicative ZipList where
    (ZipList fs) <*> (ZipList xs) = ZipList (zipWith ($) fs xs)
    pure x                        = -- left as exercise, it might surprise you :)

所以我们可以在真正的Haskell中执行上述操作:

f <$> ZipList [1,2,3] <*> ZipList [4,5,6] <*> ZipList [7,8,9]

遗憾的是,这比原始版本稍微冗长一点 - 而且比

更冗长
zipWith3 f [1,2,3] [4,5,6] [7,8,9]

但“优势”是你可以做任何基本上任意固定的“提升”:)

这里要带走的真实情况是,这是“Applicative”发明要解决的“那种模式”;这是一个非常常见的模式/领域,特别是蓬勃发展,并且开始建立直觉以便能够发现可能非常适合的问题的告诉迹象可能会很好一个适用的解决方案。