我几乎可以通过我的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]
Run Code Online (Sandbox Code Playgroud)
得到f 1
,f 2
等等,我可以做2个args
map (uncurry f) $ zip [1..3] [6..8].
Run Code Online (Sandbox Code Playgroud)
现在我可以做3个args
map (uncurry $ uncurry f) $ zip (zip [1..3] [6..8]) [3..5]
Run Code Online (Sandbox Code Playgroud)
但是这种速度非常快.是否有更优雅(或惯用)的方式(除了使我自己的"uncurry3"功能配对zip3
)?我总是遇到一个优雅的Haskell解决方案,这看起来非常笨拙.
对不起,如果这是一个新手问题或之前已经回答过.谢谢.
GS *_*ica 12
您可以使用以下命令缩短2参数代码zipWith
:
zipWith f [1..3] [6..8]
Run Code Online (Sandbox Code Playgroud)
而且方便的是zipWith3
,标准库中实际上有一个(以及最多7个).
也有平行的列表内涵与-XParallelListComp
这似乎去到任何数量:
[f a b c | a <- [1..3] | b <- [6..8] | c <- [3..5]]
Run Code Online (Sandbox Code Playgroud)
这实际上是为列表定义Applicative实例的一种方法.
回想一下,Applicative的定义围绕以下定义(<*>)
:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
如果你专注[]
,你可以得到:
(<*>) :: [a -> b] -> [a] -> [b]
Run Code Online (Sandbox Code Playgroud)
也许这开始看起来像你可以做到这一点的方式?您有一个函数列表,您可以将它们应用于值列表.也许我们可以通过(<*>)
某种方式开展工作,以便将函数列表应用于值列表,如zip:
fs <*> xs = zipWith ($) fs xs
Run Code Online (Sandbox Code Playgroud)
回想一下($)
,函数应用程序运算符:
($) :: (a -> b) -> a -> b
f $ x = f x
Run Code Online (Sandbox Code Playgroud)
因此,zipWith
"压缩"函数列表和值列表,并返回将每个函数应用于相应值的结果.
我想你应该可以从这里拿走它.让我们将两个列表加在一起:
(fmap (+) [1,2,3]) <*> [4,5,6]
Run Code Online (Sandbox Code Playgroud)
变成了
[(1+), (2+), (3+)] <*> [4,5,6]
Run Code Online (Sandbox Code Playgroud)
变成了
[1+4, 2+5, 3+6]
Run Code Online (Sandbox Code Playgroud)
和
[5, 7, 9]
Run Code Online (Sandbox Code Playgroud)
三个参数函数怎么样?
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]
Run Code Online (Sandbox Code Playgroud)
整齐!
不难看出你可以通过接受另一个来扩展它到任意性功能(<*>)
.
此外,我们可以为fmap
正确的固定定义一个方便的别名并调用它(<$>)
,并定义(<*>)
为具有不需要括号的正确固定,我们可以做类似的事情
f <$> [1,2,3] <*> [4,5,6] <*> [7,8,9]
Run Code Online (Sandbox Code Playgroud)
哪个很整洁,对吧?现在你可以基本上做一个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 :)
Run Code Online (Sandbox Code Playgroud)
所以我们可以在真正的Haskell中完成上述操作:
f <$> ZipList [1,2,3] <*> ZipList [4,5,6] <*> ZipList [7,8,9]
Run Code Online (Sandbox Code Playgroud)
不幸的是,这比原始版本稍微冗长一点 - 而且比...更冗长
zipWith3 f [1,2,3] [4,5,6] [7,8,9]
Run Code Online (Sandbox Code Playgroud)
但"优势"是你可以做任何基本上任意固定"提升":)
在这里带走的真实情况是,这是"Applicative"发明要解决的"完全一种模式"; 这是一个非常常见的模式/领域,Applicative特别茁壮成长,并且开始建立直觉以便能够发现可能适合于Applicative解决方案的问题的迹象可能是很好的.