我有一个类型为a -> b -> Bool
的函数列表,正在尝试将它们应用于两个输入,并将结果与All
或Any
组合。我使用一个变量的函数:
mconcat (map (All . ) [(<7),(>7),(==7)]) $ 6
但我不知道如何对两个变量函数进行同样的处理。
这项工作:
mconcat (map (All . ) (map (uncurry) [(<),(>),(==)])) $ (,) 6 7
但在我看来,这是一个丑陋的变通办法。
有更好的方法吗?
这是一种自己编写的代码,您只需要将类型连接起来。但是有两个标准工具(即Applicative
和Traversable
)可以用来缩短代码。
Data.Traversable
模块中存在sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
。当专门研究[]
的Traversable
的实例和函数应用(->) r
时,我们得到:
sequenceA :: [r -> a] -> r -> [a]
因此,sequenceA
将->
从[]
中拉出来,将列表中的每个函数应用于一个固定的参数。
sequenceA [(< 7), (> 7), (== 7)] :: (Num n, Ord n) => n -> [Bool]
-- equivalent to:
n -> map ($ n) [(< 7), (> 7), (== 7)]
所以你的第一个函数可以写成
f :: (Num n, Ord n) => n -> Bool
f = and . sequenceA [(< 7), (> 7), (== 7)]
我用的是and
而不是mconcat . map (All .)
。
对于第二个函数,uncurry
是正确的工具。我们必须将uncurry
映射到二进制函数列表上,以获得元组的一元函数列表,这样我们就可以使用sequenceA
来提升单个参数。因为traverse f = sequenceA . map f
,我们可以把它写成:
g :: Ord n => n -> n -> Bool
g = curry $ and . traverse uncurry [(<), (>), (==)]
(注意,对于任何正确实现的Ord
实例,>
和<
应该是互斥的。因此,这两个函数都将始终返回False
。)
原始代码的一个替代方案:
mconcat (map (f a b -> All (f a b)) [(<),(<=)]) 3 4
可以用毫无意义的风格进一步重写f a b -> All (f a b)
:
mconcat (map ((.) (All .)) [(<),(<=)]) 3 4