理解函数的签名(。)(:)

时间:2018-06-17 20:03:50

标签: haskell

你能解释一下我们是怎么做的:

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]

更一般来说,我有时害怕(。)就像我感觉不直观,如果你有一些技巧可以分享以更好地感受它,欢迎: - )

4 个答案:

答案 0 :(得分:5)

首先,让我们重命名一些东西并加上括号:

(:) :: d -> ([d] -> [d])

现在,在表达(.) (:)中,(:)(.)的第一个参数。 (.)的第一个参数应该是b -> c类型。因此,

b -> c = d -> ([d] -> [d])

表示

b = d
c = [d] -> [d]

(.) (:)的结果类型为(a -> b) -> a -> c。将bc放入,我们得到

(a -> d) -> a -> ([d] -> [d])

这正是ghci告诉你的,除了重命名为a1 = aa2 = 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)。