我有三个函数(getRow
、getColumn
、getBlock
),有两个参数(x和y),每个参数产生相同类型的列表。我想编写第四个函数来连接它们的输出:
outputList :: Int -> Int -> [Maybe Int]
outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock]
该函数有效,但是有没有办法将双映射(带有三个"$")重写为单个映射?
import Data.Monoid
outputList :: Int -> Int -> [Maybe Int]
outputList = mconcat [getRow, getColumn, getBlock]
你应该得到一个解释。
首先,我将明确指出所有这些函数都具有相同的类型。
outputList, getRow, getColumn, getBlock :: Int -> Int -> [Maybe Int]
现在让我们从您的原始定义开始。
outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock]
这些函数产生一个[Maybe Int]
,任何事物的列表都是幺半群。单体组合列表与连接列表相同,因此我们可以将concat
替换为 mconcat
。
outputList x y = mconcat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock]
另一个幺半群是函数,如果它的结果是一个幺半群。也就是说,如果b
是幺半群,那么a -> b
也是幺半群。单体组合函数与调用具有相同参数的函数,然后以幺半组合结果相同。
因此,我们可以简化为
outputList x = mconcat $ map ($ x) [getRow,getColumn,getBlock]
然后再次到
outputList = mconcat [getRow,getColumn,getBlock]
大功告成!
Typeclassopedia有一个关于幺半群的部分,尽管在这种情况下,我不确定它是否增加了Data.Monoid文档之外的内容。
作为第一步,我们观察到您的定义
outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock]
可以使用函数组合运算符(.)
而不是函数应用程序运算符($)
重写,如下所示。
outputList x y = (concat . map ($ y) . map ($ x)) [getRow,getColumn,getBlock]
接下来我们注意到map
是列表中fmap
的另一个名称,并且满足fmap
法律,因此,特别是,我们有 map (f . g) == map f . map g
.我们应用此定律来定义使用单个应用程序的版本 map
.
outputList x y = (concat . map (($ y) . ($ x))) [getRow,getColumn,getBlock]
作为最后一步,我们可以用 concatMap
替换concat
和map
的组成。
outputList x y = concatMap (($ y) . ($ x)) [getRow,getColumn,getBlock]
最后,在我看来,虽然Haskell程序员倾向于使用许多花哨的运算符,但用
outputList x y = concatMap (f -> f x y) [getRow,getColumn,getBlock]
正如它清楚地表达的那样,该函数的作用。但是,使用类型类抽象(如另一个答案所示)可能是一件好事,因为您可能会观察到您的问题具有一定的抽象结构并获得新的见解。
我会同意@dave4420的答案,因为它最简洁,准确地表达了你的意思。但是,如果您不想依赖Data.Monoid
那么您可以重写如下
原始代码:
outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock]
融合两张地图:
outputList x y = concat . map (($y) . ($x)) [getRow,getColumn,getBlock]
将concat . map
替换为concatMap
:
outputList x y = concatMap (($y) . ($x)) [getRow,getColumn,getBlock]
大功告成。
编辑:aaaa,这与克里斯蒂安森@Jan答案完全相同。哦,好吧!