如何理解Haskell中的函数“(。)(。)”

我是Haskell的初学者,我遇到了函数(.)(.) ,我使用:t在GHCi中获得它的类型:

:t (.)(.)
(.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c

如何在这里理解类型(.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c ? 我很困扰。


这是组合运算符对组合运算符本身的部分应用。 一般来说,我们知道如果我们将(.)应用于某些函数f :: x -> y ,那么

>>> :t (.) f
(.) f :: (a -> x) -> a -> y

由于类型的排列方式:

(b -> c) -> (a -> b) -> a -> c
 x -> y
--------------------------------
            (a -> x) -> a -> y

我们删除第一个参数,并用给定参数的相应类型替换bc剩余事件。

在这里, f(.) ,这意味着我们确定x ~ (b -> c)y ~ (a -> b) -> a -> c 。 再次排列类型

(a ->   x   )  -> a ->              y
      b -> c               (a -> b) -> a -> c

由于a在顶部和底部出现时,我们需要选择一个新的变量名称为a底部; GHC选择了a1

(a ->   x   )  -> a ->               y
      b -> c               (a1 -> b) -> a1 -> c

把它们放在一起会产生你在GHCi中看到的类型。

(a -> b -> c) -> a -> (a1 -> b) -> a1 -> c

抛开解剖玩笑,什么是(.)(.)

假设你有一个函数f :: a -> b ,但你想要一个函数g :: a -> c ,也就是说,你需要f但是具有不同的返回类型。 你唯一能做的就是找到一个帮助函数h :: b -> c ,它将为你转换返回值。 你的函数g就是hf的组合:

g = h . f

然而,你可能有一个更通用的函数h' :: t -> b -> c ,它可以以多种方式将b类型的值转换为c类型的值,具体取决于某些参数x :: t的值。 然后,你可以得到很多不同的g小号根据这样的说法。

g = (h' x) . f

现在,给定h'xf ,我们可以返回g ,所以让我们编写一个函数来做到这一点:一个函数将f的返回值从b类型值提升为c类型值,给定一个函数h'和一些值x

promote h' x f = (h' x) . f

您可以机械地将任何函数转换为无点形式; 我不熟悉细节,但使用PointFree.io生成

promote = ((.) .)

这只是部分应用程序(.) (.)写成段,即:

((.) (.)) h' x f == (h' x) . f

所以,我们的“胸部”运算符只是一个广义的预组合运算符。


函数(.)具有类型(b -> c) -> (a -> b) -> (a -> c) ,即给出两个函数,一个从ab ,一个从bc ,他们一起形成一个单一的a c功能。

让我们再次写出(.)的类型,但用不同的字母来区分它们: (y -> z) -> (x -> y) -> (x -> z) 。 假设abc版本是(.)中的第一个(.)(.) ,而xyz版本是第二个。 我们把第二个作为第一个参数传递给第一个参数。 请记住第一个参数的类型(b -> c) ,所以我们需要将其与第二个函数的类型进行匹配。

您可能会注意到这里存在不匹配: (b -> c)是一个接受一个参数的函数,但(.)需要两个参数。 但是在Haskell中,所有函数都是curried的,这意味着一个带两个参数的函数与一个带有一个参数并返回另一个带有一个参数的函数(原始的两个参数的第二个)的函数是一样的,并且只有该函数然后返回实际结果。

换句话说,箭头类型的构造函数从右到左绑定,我们可以放在括号中以使其更清晰:为了我们的目的,第二个(.)类型更好地写为(y -> z) -> ((x -> y) -> (x -> z)) 。 使之与(b -> c)匹配,很明显这意味着b = (y -> z)c = ((x -> y) -> (x -> z))

第一个参数给出的结果是第一个(.)类型的剩余部分,其中类型变量被我们的替换替换 - (.)(.)类型因此是(a -> (y -> z)) -> (a -> ((x -> y) -> (x -> z)))

现在我们可以放下箭头右侧的所有圆括号来简化这个表达式并得到(a -> y -> z) -> a -> (x -> y) -> x -> z 。 很容易看出,这完全是(模重命名)GHCi给你的。

这种类型和函数的意思是,“给定一个二元函数b ,它接受ay并返回z ,并给出一个类型a a的值va ,并且给定一个一元函数u ,它接受一个x并返回一个y ,并最终给出一个x类型的值vx ,给我由计算b va (u vx)得到的z

你可能不需要它。 该功能有趣的唯一原因是它看起来像胸部。


           ┌──────────────────────────────────────────────────────────────────────────┐ 
           │                                                                          │
      ┌─────────────────────────────────────────────────┐                             │
      │    │                                            │                             │
 ┌─────────────────────────┐                      ┌──────────────────────┐            │
 │    │    │               │                      │     │                │            │
 ↓    ↓    ↓               │                      ↓     │                │            │ 
(a -> b -> c)      ->      a          ->         (a1 -> b)        ->     a1     ->    c
 ───────────              ───                     ───────                ──        
      ↓                    ↓                         ↓                   ↓
     (f)                  (x)                       (g)                 (y)
      ↓                    ↓                         ↓                   ↓
a function        a thing that works      a function of one        a thing that
of two arguments  as the first argument   argument that            works as the
that returns      of f                    returns a thing          argument of g
the same type                             suitable as the second
(.)(.) returns                            argument of f

现在我们如何将这四件事结合起来?

首先,我们可以将f应用于x 。 这给了我们什么? 一个参数的函数。 它的类型应该是b->c ,因为我们刚才应用类型的功能a->b->c到类型的参数a

然后我们可以取第二个g并将其应用于y 。 这给了我们一些类型b

然后我们可以采取类型的函数b->c在第一步骤中计算,并将其应用到类型的那个东西b在第二步骤中计算。 这给了我们一些类型c ,整个(.)(.)构造的结果类型,这正是我们需要的。

注意所有这些都可以通过查看类型来发现。 无需知道该功能最初是如何实施的。

链接地址: http://www.djcxy.com/p/93011.html

上一篇: How to understand the function "(.)(.)" in Haskell

下一篇: Is it okay to attach async event handler to System.Timers.Timer?