如何确定Haskell函数的类型?

时间:2012-07-24 19:46:12

标签: haskell functional-programming

我偶然发现了许多练习,这些练习会给你一个功能,并要求你推断出每个练习的类型。

我有以下示例。请注意,这不是我需要完成的功课。我有这个具体例子的答案,并在下面提供。也许有人可以帮助我学习如何推理这种练习。

功能:

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

来了解类型

4 个答案:

答案 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)。在这里,您可以立即看到fg是带有两个参数的函数,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。但是,它包含一些拼写错误,因此您在阅读时必须保持警惕。