你能解释一下我们是怎么做的:
Prelude Data.Monoid> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
Prelude Data.Monoid> :t (:)
(:) :: a -> [a] -> [a]
到那个:
Prelude Data.Monoid> :t (.)(:)
(.)(:) :: (a1 -> a2) -> a1 -> [a2] -> [a2]
更一般来说,我有时害怕(。)就像我感觉不直观,如果你有一些技巧可以分享以更好地感受它,欢迎: - )
答案 0 :(得分:5)
首先,让我们重命名一些东西并加上括号:
(:) :: d -> ([d] -> [d])
现在,在表达(.) (:)
中,(:)
是(.)
的第一个参数。 (.)
的第一个参数应该是b -> c
类型。因此,
b -> c = d -> ([d] -> [d])
表示
b = d
c = [d] -> [d]
(.) (:)
的结果类型为(a -> b) -> a -> c
。将b
和c
放入,我们得到
(a -> d) -> a -> ([d] -> [d])
这正是ghci告诉你的,除了重命名为a1 = a
和a2 = d
的类型变量。
答案 1 :(得分:2)
好吧,让我们进行类型推断。因此,我们有两个功能:
(.) :: (b -> c) -> (a -> b) -> a -> c
(:) :: d -> [d] -> [d]
我们这里使用d
,因为a
中的(.)
本身不是 a
中的(:)
,所以我们通过使用两个单独的类型变量来避免混淆。
更典型形式的类型签名是:
(.) :: (b -> c) -> ((a -> b) -> (a -> c))
(:) :: d -> ([d] -> [d])
所以现在(:)
是函数应用程序的参数,(.)
作为函数,我们知道(:)
的类型是参数的类型 (.)
,这意味着d -> ([d] -> [d]) ~ (b -> c)
(此处代字号~
表示它是相同的类型)。因此,我们知道:
b -> c
~ d -> ([d] -> [d])
---------------------
b ~ d, c ~ [d] -> [d]
这意味着b
类型签名中的(.)
与d
类型签名中的(:)
类型相同,c ~ [d] -> [d]
}。
因此,我们得到:
(.) (:) :: (a -> b) -> (a -> c))
= (.) (:) :: (a -> d) -> (a -> ([d] -> [d])))
= (.) (:) :: (a -> d) -> (a -> [d] -> [d])
= (.) (:) :: (a -> d) -> a -> [d] -> [d]
答案 2 :(得分:0)
(.)
无所畏惧。这是最自然的事情。
想象一下,你定义一个抽象类"连接":
class Connecting conn where
plug :: conn a b -> conn b c -> conn a c
noop :: conn a a
conn a b
是一种某种,它将点 连接到点 b'/ em>的。鉴于可以将 a 连接到 b ,将 b 连接到 c ,它最自然地为我们提供了只需将两个 somethings 连接在一起,即可将 连接到 c 。此外,任何 point 可以连接到另一个,并且可以连接,显然可以被认为是完全没有努力连接到自己。
功能正在连接。只需使用一个的输出作为另一个的输入。如果我们有这两个兼容的功能,那么以这种方式插入它们就可以为我们提供组合连接。
函数are Connecting
:
instance Connecting (->) where
-- plug :: (->) a b -> (->) b c -> (->) a c
(plug f g) x = g (f x)
-- noop :: (->) a a
noop x = x -- what else? seriously. All we have is x.
关于plug :: (->) a b -> (->) b c -> (->) a c
的有趣之处。当我们考虑所涉及的类型时,这种参数的顺序是最合适的。但从它的定义来看,我们更喜欢将其定义为
gulp :: (->) b c -> (->) a b -> (->) a c
(gulp g f) x = g (f x)
现在这个定义最有意义,但这种类型感觉有点折磨。
没关系,我们可以both:
(f :: a -> b) >>> (g :: b -> c) :: -- ...
a -> c
(g :: b -> c) <<< (f :: a -> b) :: -- ...
a ->
c
巧合的是,<<<
也称为(.)
。
很明显,我们有(.) = (<<<) = flip (>>>)
,所以(g . f) x = g (f x) = (f >>> g) x
。
以g . f
呈现,通常更容易处理等效表达式f >>> g
,类型明智。只需垂直对齐类型
(:) :: b -> ([b] -> [b])
f :: a -> b
f >>> (:) :: a -> ([b] -> [b])
这样
(>>> (:)) :: (a -> b) -- = ((:) <<<) = ((:) .) = (.) (:)
-> a -> ([b] -> [b])
because (>>> (:)) = (\f -> (f >>> (:)))
。
哪个是(\f -> ((:) <<< f)) = (\f -> ((:) . f)) = ((:) .) = (.) (:)
。
编辑:例如,(Endo . (:)) = ((:) >>> Endo)
的类型很容易到达,其中包含:
Endo :: ( b -> b ) -> Endo b
(:) :: a -> ([a] -> [a]) -- b ~ [a]
(:) >>> Endo :: a -> Endo [a]
有关Endo
的更多信息,请在GHCi提示下同时尝试:t Endo
和:i Endo
,如果需要,请阅读Haskell's record syntax。
答案 3 :(得分:0)
将(.) (:)
作为运算符部分((:) .)
进行编写,强调它将(:)
与其他函数进行后期合并(即通过将(:)
应用于其中来修改函数结果 - \g -> (g .)
对于函数是fmap
。对称地,(. (:))
预先组合(:)
以及其他函数(\f -> (. f)
为contramap
用于函数 - cf. the Op
newtype)。