我正在研究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
我看不到结果类型签名是如何发生的。如果给我该函数并要求编写类型签名,那我将无可救药。
以下内容也一样。在“遍历”一章中,我们被告知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
的类型签名?
在这里超级迷路了,任何帮助将不胜感激。
答案 0 :(得分:7)
也许只是在视觉上对齐类型会使您对管道的进展方式有所了解,并帮助您迈向下一个混乱点!
display:block
要在屏幕上保留下一个,我们将(==) :: Eq a => a -> (a -> Bool)
any :: (a -> Bool) -> (t a -> Bool)
any . (==) :: Eq a => a -> (t a -> Bool)
缩写为Traversable
,将T
缩写为Applicative
。您的问题中还有两个A
,一个在计算级别,一个在类型级别。为避免混淆,我将改为将您的计算级别f
重命名为f
。因此,如果g
代表某个g :: a -> f b
:
Applicative f
(等等!fmap g :: T t => t a -> t (f b)
sequenceA :: (T t, A f) => t (f b) -> f (t b)
sequenceA . fmap g :: (T t, A f) => t a -> f (t b)
\g -> sequenceA . fmap g :: (T t, A f) => (a -> f b) -> t a -> f (t b)
为何对fmap g
的约束是t
而不是Traversable
?好吧,没问题:我们实际上可以给它更宽松的条件类型Functor
。但是由于每个fmap g :: Functor t => ...
必须是一个Traversable
,所以也可以指定这种类型,这样可以使并行性更加清晰。)
答案 1 :(得分:3)
All Haskell functions take just one argument-甚至我们经常认为带有多个参数的那些。考虑您的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
(==)
的类型可以读为(==) :: Eq a => a -> (a -> Bool)
:它需要一个a
值(a
可以是Eq
的任何实例)并提供一个a -> Bool
函数。 any
依次接受a -> Bool
函数(a
可以是任何东西)并赋予t a -> Bool
函数(t
可以是作为...的实例的任何东西Foldable
)。就是这样,any . (==)
管道中的中间类型是Eq a => a -> Bool
。