【发布时间】:2019-05-12 12:19:46
【问题描述】:
我正在阅读 Haskell 的书,我意识到我很难理解函数组合。在一个非常基本的层面上,我有一个管道的心理模型,它接受输入并将其结果传递给组合中的下一个函数。对于简单的功能,这很容易。
我遇到困难的地方是理解组成函数的结果类型签名是如何形成的。例如,如果我们查看elem 的基本定义:
elem :: (Foldable t, Eq a) => a -> t a -> Bool
elem = any . (==)
>:t (==)
(==) :: Eq a => a -> a -> Bool
>:t any
any :: Foldable t => (a -> Bool) -> t a -> Bool
我看不到生成的类型签名是如何发生的。如果给我这个函数并要求我写类型签名,我会绝望地迷路。
以下内容也是如此。在 Traversable 一章中,我们被告知 traverse 只是 sequenceA 和 fmap 组成:
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
>:t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
>:t sequenceA
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
我自己理解每个函数的类型签名,但是它们如何组合来创建traverse 的类型签名?
在这里超级迷路,任何帮助将不胜感激。
【问题讨论】:
-
不知道如何解释这些特定的例子,但一般来说,我认为你应该练习 η-expanding/reducing 表达式与组合,以感受它。
-
请记住,
(a -> Bool)也可以用类似b的类型变量来表示,这意味着您可以将a -> a -> Bool视为a -> b,将(a -> Bool) -> t a -> Bool视为b -> t a -> Bool。通过将t a -> Bool命名为c进一步扩展,(a -> Bool) -> t a -> Bool变为b -> c。所以总的来说,这两者很容易组合。 -
sequenceA不是与fmap组成的;它由fmap f组成。区别很重要。 -
为了更好地理解事物是如何构成的,请尝试扩展定义:
elem = any . (==)(.)的定义等于elem = \x -> any ((==) x),它可以是“eta-expanded”(将两边缺少的参数和参数)添加到elem = \x xs -> any ((==) x) xs中,我希望其含义更明显。加上一点语法糖,这是elem x xs = any (x ==) xs。
标签: haskell