我偶然发现了许多练习,这些练习会给你一个功能,并要求你推断出每个练习的类型。
我有以下示例。请注意,这不是我需要完成的功课。我有这个具体例子的答案,并在下面提供。也许有人可以帮助我学习如何推理这种练习。
功能:
h1 f g x y = f (g x y) x
假设的类型:
h1 :: (a -> b -> c) -> (b -> d -> a) -> b -> d -> c
谢谢!
我添加了27个练习here 没有解决方案。
其中一些人的解决方案包括here。
但是,可以使用GHCi命令:t
答案 0 :(得分:21)
h1 f g x y = f (g x y) x
所以,从左到右采取参数列表:
f
是一个应用于两个参数的函数:第一个是(g x y)
的结果,第二个是x
a
b
c
),但我们做知道这必须是h1
返回的类型
f
是一个映射a -> b -> c
g
是一个应用于两个参数的函数:第一个是x
,第二个是y
g
的第一个参数与f
的第二个参数相同,所以它必须是相同的类型:我们已经标记为b
g
的第二个参数是y
,我们尚未分配占位符类型,因此按顺序获取下一个d
g
的结果是f
的第一个参数,我们已将其标记为a
g
是一个映射b -> d -> a
x
,因为那是f
的第二个参数,我们已经标记了它的类型b
y
,这是g
的第二个参数,所以我们已经标记了其类型d
h1
的结果是将f
应用于(g x y) x
的结果,正如我们之前所说,因此它具有相同的类型,已标记为c
虽然我们按顺序完成了参数列表,但是通过查看h1
的 body 来标记,推断和统一每个参数的类型的实际过程。< / p>
所以,我的第一颗子弹可以详细说明:
f
是第一个要考虑的论点,所以让我们看看h1
的主体(=
之后的所有内容),看看它是如何使用的
f (g x y) x
表示f
已应用于(g x y) x
,因此f
必须是一个功能(g x y)
在括号中,这意味着正在评估这些括号中的内容,并且该评估的结果是f
的参数x
只是f
的简单参数,直接来自h1
自己的参数列表f
是一个带有两个参数的函数如果它有助于阅读f (g x y) x
,您可以认为类似C的表示法中的等效表达式为f(g(x,y), x)
。在这里,您可以立即看到f
和g
是带有两个参数的函数,f
的第一个参数是g
返回的等等。
请注意,表达式h1 f g x y
的左侧仅提供一条类型信息:h1
是四个参数的函数。参数名称本身只是表达式右侧使用的占位符(h1
的主体)。这里参数的相对排序告诉我们如何调用h1
,但没有关于h1
如何在内部使用参数。
同样,这是一个程序风格的等价物(我将使用Python,所以我不必填写任何类型):
def h1(f, g, x, y):
return f(g(x,y),x)
这意味着完全与
相同h1 f g x y = f (g x y) x
(有一个警告 - 部分申请 - 我怀疑这只会在这里进一步混淆)。
在这两种情况下,声明(Haskell中=
的左侧,Python中的:
之前)只告诉我们函数名称及其所需的参数数量。
在这两种情况下,我们都可以从定义(Haskell中的右侧,Python中:
之后的缩进块)中推断 f
和{{1}两个参数都是函数,g
的第一个参数与g
的第二个参数相同,等等。在Haskell中,编译器为我们做了这个;如果我们用错误数量的参数调用f
,或者返回g
不能用作第一个参数的东西,Python就会在运行时抱怨。
答案 1 :(得分:8)
如果你没有任何东西,你可以逐步从使用方式中推断出类型,统一推导出的部分。我们有定义
h f g x y = f (g x y) x
所以我们看到h
接受了迄今为止完全未知类型的四个参数,让我们称之为a, b, c, d
,结果类型应该被称为r
。所以h的类型必须与
a -> b -> c -> d -> r
现在我们必须看看我们可以对参数类型说些什么。为此,我们看一下定义的右侧。
f (g x y) x
所以f
应用于两个参数,第二个参数是h
的第三个参数,因此我们知道它的类型是c
。我们对f
的第一个论点的类型一无所知。应用f
的结果是应用h
的结果,因此f
的结果类型为r
。
f :: u -> c -> r
a = u -> c -> r
右侧f
的第一个参数是g x y
。因此我们可以推断出
g :: c -> d -> u
b = c -> d -> u
因为g
的参数是h
的第三和第四个参数,因此它们的类型是已知的,结果是f
的第一个参数。
结束,
f :: u -> c -> r
g :: c -> d -> u
x :: c
y :: d
h :: (u -> c -> r) -> (c -> d -> u) -> c -> d -> r
根据需要重命名类型变量。
答案 2 :(得分:5)
如果您想以正式方式执行此操作,则可以遵循类型系统的推理规则。如果您只有应用程序,那么简单类型的lambda演算的规则就可以了(参见lambda演算中lecture notes的第2.3节)。
该算法包括使用类型系统的输入规则构建演绎树,然后通过统一计算最终的术语类型。
答案 3 :(得分:0)
确定函数的类型取决于所使用的类型系统。大多数函数式编程语言都基于Hindley-Milner type system,其中有type inference算法(也称为Hindley-Milner)。对于给定的表达式,算法派生所谓的原则类型,它基本上是函数可以具有的最通用类型,或者如果表达式是不可分类的则失败。这就是GHCi在您输入:t expression
时使用的内容。
Hindley-Milner类型系统允许多态函数,但所有通用量词都必须在类型之外。 (通常在使用类型时看不到量词;省略量词并且仅假设它们。)例如,const
具有类型a -> (b -> a)
,可以使用量词(∀a)(∀b)(a -> (b -> a))
编写。 1}}。但是,H-M不允许类似(∀a)(a -> (((∀b)b) -> a))
有更多富有表现力的类型系统,例如System F,它允许任何地方的类型变量的通用量词,但它的类型推断是不可判定的,并且没有适当的原理类型概念。 GHC中有各种语言扩展允许您使用这些类型,但是当您丢失类型推断时,您必须使用类型明确地注释您的函数。
例如:在H-M中,函数xx = \f -> f f
是不可分类的(在GHCi中尝试)。但是在一个允许通用类型量化器的类型系统中,它有一种类型。在具有适当GHC扩展的Haskell中,您可以编写:
{-# LANGUAGE RankNTypes #-}
xx :: (forall a. a -> a) -> (forall b. b -> b)
xx = \f -> f f
(请注意,RankNTypes
允许您编写此类量化类型,但无法为此类函数提供类型推断 - 您必须自己说明类型。)
Hindley-Milner类型系统是一个很好的权衡:它允许多态函数和类型推断。
如果您有兴趣,可以阅读original paper。但是,它包含一些拼写错误,因此您在阅读时必须保持警惕。