函数式编程中的签名/类型(OCaml)

时间:2010-11-17 00:15:36

标签: functional-programming ocaml signature

我开始学习函数式编程(OCaml),但我不了解关于fp的一个重要主题:签名(我不确定它是否是一个正确的名称)。当我输入内容并使用ocaml编译时,我得到了例如:

# let inc x = x + 1 ;;
val inc : int -> int = <fun>

这是微不足道的,但我不知道,为什么会这样:

let something f g a b = f a (g a b)

给出一个输出:

val something : (’a -> ’b -> ’c) -> (’a -> ’d -> ’b) -> ’a -> ’d -> ’c = <fun>

我想,对于很多人来说,这个主题绝对是fp的基础,但我在这里寻求帮助,因为我没有在互联网上找到关于OCaml签名的任何(有一些关于Haskell签名的文章,但没有解释。)

如果这个主题以某种方式存活下来,我会在这里发布几个函数,这些签名让我感到困惑:

# let nie f a b = f b a ;; (* flip *)
val nie : (’a -> ’b -> ’c) -> ’b -> ’a -> ’c = <fun>

# let i f g a b = f (g a b) b ;;
val i : (’a -> ’b -> ’c) -> (’d -> ’b -> ’a) -> ’d -> ’b -> ’c = <fun>


# let s x y z = x z (y z) ;;
val s : (’a -> ’b -> ’c) -> (’a -> ’b) -> ’a -> ’c = <fun>

# let callCC f k = f (fun c d -> k c) k ;;
val callCC : ((’a -> ’b -> ’c) -> (’a -> ’c) -> ’d) -> (’a -> ’c) -> ’d = <fun>

感谢您的帮助和解释。

2 个答案:

答案 0 :(得分:17)

为理解这种类型的签名,您需要理解几个概念,我不知道您已经做了哪些,所以我尽力解释每个重要的概念:

柯里

如您所知,如果您的类型为foo -> bar,则会描述一个函数,该函数采用类型foo的参数并返回类型bar的结果。由于->是右关联的,因此类型foo -> bar -> bazfoo -> (bar -> baz)相同,因此描述了一个带有foo类型参数并返回类型{{1}的值的函数}},表示返回值是一个取值为bar -> baz的值并返回bar类型值的函数。

这样的函数可以像baz一样调用,因为函数应用程序是左关联的,与my_function my_foo my_bar相同,即它将(my_function my_foo) my_bar应用于参数my_function然后将作为结果返回的函数应用于参数my_foo

因为可以像这样调用它,类型my_bar的函数通常被称为“带两个参数的函数”,我将在本答案的其余部分中这样做。

输入变量

如果您定义类似foo -> bar -> baz的函数,则其类型为let f x = x。但是'a -> 'a实际上并不是OCaml标准库中任何地方定义的类型,那么它是什么?

任何以'a开头的类型都是所谓的类型变量。类型变量可以代表任何可能的类型。因此,在上面的示例中,'可以使用fintstring或任何内容进行调用 - 这无关紧要。

此外,如果同一类型变量多次出现在类型签名中,则它将代表相同的类型。所以在上面的例子中,这意味着list的返回类型与参数类型相同。因此,如果使用f调用f,则会返回int。如果使用int调用它,则会返回string,依此类推。

因此类型string的函数可以接受任何类型的两个参数(第一个和第二个参数的类型可能不同)并返回与第一个参数相同类型的值,而类型'a -> 'b -> 'a的函数将采用两个相同类型的参数。

关于类型推断的一个注意事项:除非您明确地为函数提供类型签名,否则OCaml将始终推断出最常用的类型。因此,除非函数使用任何仅适用于给定类型的操作(例如'a -> 'a -> 'a),否则推断类型将包含类型变量。

现在解释一下......

+

此类型签名告诉您val something : ('a -> 'b -> 'c) -> ('a -> 'd -> 'b) -> 'a -> 'd -> 'c = <fun> 是一个带有四个参数的函数。

第一个参数的类型是something。即一个函数,它接受任意和可能不同类型的两个参数,并返回一个任意类型的值。

第二个参数的类型是'a -> 'b -> 'c。这又是一个带有两个参数的函数。这里需要注意的重要一点是,函数的第一个参数必须与第一个函数的第一个参数具有相同的类型,并且函数的返回值必须与第一个函数的第二个参数具有相同的类型。

第三个参数的类型是'a -> 'd -> 'b,它也是两个函数的第一个参数的类型。

最后,第四个参数的类型是'a,它是第二个函数的第二个参数的类型。

返回值的类型为'd,即第一个函数的返回类型。

答案 1 :(得分:6)

如果你真的对这个主题感兴趣(并且可以访问大学图书馆),请阅读Wadler的优秀(如果有些过时)“函数式编程简介”。它以非常好的可读方式解释了类型签名和类型推断。

另外两个提示:注意 - &gt;箭头是右关联的,因此您可以从右侧对事物进行括号处理,这有时有助于理解事物,即a -> b -> ca -> (b -> c)相同。这与第二个提示相关:高阶函数。你可以做像

这样的事情
let add x y = x + y
let inc = add 1

所以在FP中,将“添加”视为必须获取两个数值参数并返回数值的函数通常不是正确的事情:它也可以是一个函数,它接受一个数字参数并返回类型为num - &gt;的函数。 NUM。

理解这将有助于您了解类型签名,但您可以在不使用的情况下执行此操作。在这里,快速简便:

# let s x y z = x z (y z) ;;
val s : (’a -> ’b -> ’c) -> (’a -> ’b) -> ’a -> ’c = <fun>

看右边。 y被赋予一个参数,因此它的类型为a -> b,其中a是z的类型。 x有两个参数,第一个参数是z,所以第一个参数的类型也必须是a(y z)的类型(第二个参数)为b,因此x的类型为(a -> b -> c)。这允许您立即推断出s的类型。