了解函数的签名 (.) (:).

  • 本文关键字:函数 了解 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]

更一般地说,我有时害怕(.(,就像我直觉上没有感觉到一样,如果你有一些技巧可以分享以更好地感受它,这是受欢迎的:-(

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

(:) :: 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

好吧,让我们进行类型推断。因此,我们有两个功能:

(.) :: (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]

(.) (:)写为运算符部分,((:) .),强调它与其他一些函数(:)进行后组合(即它通过将(:)应用于其结果来修改函数 -g -> (g .)函数fmap(。对称地,(. (:))(:)与其他一些函数(f -> (. f)contramap函数 - 参见Opnewtype(。

(.)没什么好怕的。这是最自然的事情。

假设你定义了一个抽象的"连接"类:

class Connecting conn where
plug :: conn a b -> conn b c -> conn a c
noop :: conn a a

conn a b是一种将A点连接到B的东西。考虑到将 a 连接到 b,以及 b连接到 c 的可能性,它最自然地给了我们通过将两个东西插入在一起来连接ac的可能性。此外,任何可以连接到另一个点并且可以连接到的显然可以毫不费力地被视为与自身相连。

功能正在连接。只需将一个的输出用作另一个的输入。如果我们有这样一个两个兼容的功能,以这种方式插入它们会给我们一个组合连接。

功能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)

现在这个定义最有意义,但类型感觉有点折磨。

没关系,我们可以同时拥有:

(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])

因为(>>> (:)) = (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的记录语法。

最新更新