我是Haskell的初学者,我遇到函数(.)(.)
,我使用:t
来获取GHCi中的类型:
:t (.)(.)
(.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c
如何理解(.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c
类型?我很困惑。
答案 0 :(得分:2)
函数(.)
的类型为(b -> c) -> (a -> b) -> (a -> c)
,即有两个函数,一个来自a
到b
,另一个来自b
到{{1} },它将它们粘在一起形成一个c
到a
函数。
让我们再次写出c
的类型,但使用不同的字母来区分它们:(.)
。假设(y -> z) -> (x -> y) -> (x -> z)
版本是a b c
中的第一个(.)
,而(.)(.)
版本是第二个版本。我们将第二个作为第一个参数传递给第一个参数。请记住,第一个参数的第一个参数的类型为x y z
,因此我们需要将其与第二个函数的类型进行匹配。
您可能会注意到这里存在不匹配:(b -> c)
是一个接受一个参数的函数,但(b -> c)
需要两个参数。但是在Haskell中,所有函数都是curry,这意味着一个带两个参数的函数与一个带有一个参数并返回另一个带有一个参数的函数(原始二个中的第二个)的函数实际上是相同的,并且只有那个函数然后返回真实结果。
或者换句话说,箭头类型构造函数从右到左绑定,我们可以放入括号以使其更清晰:出于我们的目的,第二个(.)
的类型更好地写为{{1} }。将其与(.)
相匹配,很明显,这意味着(y -> z) -> ((x -> y) -> (x -> z))
和(b -> c)
。
给出第一个参数,结果是第一个b = (y -> z)
类型的剩余部分,其中类型变量被我们的替换替换 - c = ((x -> y) -> (x -> z))
的类型因此是(.)
。
现在我们可以删除箭头右侧的所有括号来简化此表达式并获取(.)(.)
。很容易看出GHCi给你的确切(模数重命名)。
这个类型和函数意味着,“给定一个二元函数(a -> (y -> z)) -> (a -> ((x -> y) -> (x -> z)))
,它接受(a -> y -> z) -> a -> (x -> y) -> x -> z
和b
并返回a
,并给出一个值{类型为y
的{1}},并提供一个z
的一元函数va
并返回a
,最后给出u
的值x
输入y
,为我提供计算vx
产生的x
。
你可能不需要它。功能有趣的唯一原因是它看起来像胸部。
答案 1 :(得分:2)
这是合成运算符部分应用于合成运算符本身。一般来说,我们知道如果我们将(.)
应用于某个函数f :: x -> y
,那么
>>> :t (.) f
(.) f :: (a -> x) -> a -> y
因为类型如何排列:
(b -> c) -> (a -> b) -> a -> c
x -> y
--------------------------------
(a -> x) -> a -> y
我们删除第一个参数,并用给定参数的相应类型替换剩余的b
和c
。
此处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
就是h
和f
:
g = h . f
但是,您可以使用更通用的功能h' :: t -> b -> c
将类型b
的值以多种方式转换为c
类型的值,具体取决于某些参数x :: t
的值。然后根据该参数可以获得许多不同的g
。
g = (h' x) . f
现在,鉴于h'
,x
和f
,我们可以返回g
,所以让我们编写一个执行此功能的函数:“促进”的函数f
从类型b
的值返回值c
的值,给定函数h'
和某个值x
:
promote h' x f = (h' x) . f
您可以将任何功能机械转换为无点形式;我不熟悉细节,但使用PointFree.io生成
promote = ((.) .)
只是部分应用程序(.) (.)
写成一个部分,即:
((.) (.)) h' x f == (h' x) . f
因此,我们的“胸部”操作员只是一个广义的预组合算子。
答案 2 :(得分:0)
┌──────────────────────────────────────────────────────────────────────────┐
│ │
┌─────────────────────────────────────────────────┐ │
│ │ │ │
┌─────────────────────────┐ ┌──────────────────────┐ │
│ │ │ │ │ │ │ │
↓ ↓ ↓ │ ↓ │ │ │
(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
类型的东西,它是整个(.)(.)
构造的结果类型,这正是我们所需要的。
注意通过查看类型可以发现所有这些。无需了解该功能最初是如何实现的。