Haskell 应用习语?
Posted
技术标签:
【中文标题】Haskell 应用习语?【英文标题】:Haskell Applicative idiom? 【发布时间】:2015-12-05 13:13:20 【问题描述】:我是 Haskell 的新手,对如何以最惯用和最清晰的方式最好地表达一些操作感到困惑。目前(还会有更多)我对<*>
感到困惑(我什至不知道该叫什么)。
例如,如果我有,说
f = (^2)
g = (+10)
作为代表函数(实际上它们更复杂,但这里的关键是它们是不同的和不同的),那么
concatMap ($ [1,2,3,4,10]) [(f <$>), (g <$>) . tail . reverse]
和
concat $ [(f <$>), (g <$>) . tail . reverse] <*> [[1,2,3,4,10]]
完成同样的事情。
是这些更惯用的 Haskell 之一,其中一个是否暗示了一个有经验的 Haskell 读者而另一个没有。也许还有其他(更好)的方式来表达完全相同的东西。像我这样的 Haskeller 新手可能会遗漏这两种方法之间的概念差异吗?
【问题讨论】:
您的输入是否只会成为一个列表?如果是这样,显然前者更好。输入是指数字列表。 @itsbruce:好点子。是的,只有一个列表。 将列表的Applicative
或Functor
实例与concat
组合起来感觉非常可疑。 Functor+return+join=Monad
,对于列表,concat=join
。因此,如果您要映射然后连接,concatMap
或 =<<
可能更简洁。
@dfeuer:听起来很有启发性(如果我理解的话;记住:初学者)。你能详细说明一下吗?
他是最好的答案。
【参考方案1】:
您的函数(f <$>)
和(g <$>).tail.reverse
都返回一个幺半群类型(在本例中为列表),因此您可以使用mconcat
将它们转换为单个函数。然后您可以将此函数直接应用于输入列表,而不是将其包装在另一个列表中并使用concatMap
:
mconcat [(f <$>), (g <$>).tail.reverse] $ [1,2,3,4,10]
为了对此进行扩展,如果b
是一个幺半群,则函数a -> b
是Monoid
的一个实例。此类功能的mappend
的implementation 是:
mappend f g x = f x `mappend` g x
或等效
mappend f g = \x -> (f x) `mappend` (g x)
所以给定两个函数f
和g
,它们返回一个幺半群类型b
,f
mappendg
返回一个函数,该函数将其参数应用于f
和g
,并使用以下方法组合结果Monoid
的 b
实例。
mconcat
的类型为 Monoid a => [a] -> a
,并使用 mappend
组合输入列表的所有元素。
列表是幺半群,其中mappend
== (++)
所以
mconcat [(f <$>), (g <$>).tail.reverse]
返回一个类似的函数
\x -> (fmap f x) ++ (((fmap g) . tail . reverse) x)
【讨论】:
这很有趣。我确实花了一些时间来理解,但the source for the function instance ofMonoid
解决了问题。 (f <$>)
的计算结果为 ([f] <*>)
,其类型为 [a] -> [a]
,其返回类型 [a]
是 Monoid
的实例,使函数本身成为 Monoid
的实例。
我说[a] -> [a]
但你对Num
约束是正确的,它也适用于这里(我很草率并省略了它)。 mconcat
用mappend
散布一个列表,所以我们得到mconcat [(f <$>), (g <$>) . tail . reverse] = \x -> (([f] <$>) x) `mappend` (((g <$>) . tail . reverse) x)
,所以列表中的所有函数都提供了mconcat func
提供的参数,然后这些函数的输出与mappend
连接。在我们的例子中,函数输出列表为mappend = (++)
,对于其他幺半群,将使用相应的mappend
。
这对我来说并不明显,我只是在检查了 Monoid
实例的功能后才知道的。尽管如此,我认为没有很多其他方法可以有意义地实现实例,所以从这个意义上说,我会说这是“显而易见的”实现。我同意它看起来很奇怪、很酷且功能强大,我个人不会在我希望其他人稍后阅读的代码中快速使用它。
我说我不会在我希望人们阅读的代码中使用它太快了。我可能会遇到这种模式准确表达我打算做的事情的情况。 (也许我会添加评论以澄清发生了什么;))
@raxacoricofallapatorius - 只有当返回类型 b
是一个幺半群时,函数才是幺半群,请参阅更新。【参考方案2】:
我个人会写你的例子
f = (^2)
g = (+10)
let xs = [1,2,3,4,10]
in (map f xs) ++ (map g . tail $ reverse xs)
在一种非常适用的“心情”中,我会将in
之后的部分替换为
((++) <$> map f <*> map g . tail . reverse) xs
在这种情况下,我实际上并不认为它更具可读性。如果您不直接理解它的含义,请花一些时间来理解 Applicative
的实例 ((->) a)
(Reader
)。
我认为选择实际上取决于您想要做什么,即您的输出应该意味着什么。在您的示例中,任务非常抽象(基本上只是展示了 Applicative
可以做什么),因此使用哪个版本并不是直接显而易见的。
[]
的 Applicative
实例直观地与组合相关,所以我会在这样的情况下使用它:
-- I want all pair combinations of 1 to 5
(,) <$> [1..5] <*> [1..5]
如果您有许多函数,并且您想尝试这些函数与多个参数的所有组合,我确实会使用Applicative
的[]
实例。但是,如果您所追求的是不同转换的串联,我会这样写(我在上面做了)。
作为一个中等经验的 Haskeller,我只有 2 美分。
【讨论】:
【参考方案3】:我有时会遇到类似的问题。你有一个元素,但有多个功能。
通常我们有多个元素和单个功能:所以我们这样做:
map f xs
但这不是 Haskell 中的问题。对偶很简单:
map ($ x) fs
事实上,你的x
实际上是一个列表,你想在map
之后concat
,所以你这样做
concatMap ($ xs) fs
我无法真正理解第二个等式中发生了什么,即使我可以使用应用定律推断它与第一个等式的作用相同。
【讨论】:
以上是关于Haskell 应用习语?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 emacs - haskell 模式下运行 haskell 应用程序?
如何用 Haskell 编写 Windows 服务应用程序?