我开始学习函数式编程(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>
感谢您的帮助和解释。
答案 0 :(得分:17)
为理解这种类型的签名,您需要理解几个概念,我不知道您已经做了哪些,所以我尽力解释每个重要的概念:
如您所知,如果您的类型为foo -> bar
,则会描述一个函数,该函数采用类型foo
的参数并返回类型bar
的结果。由于->
是右关联的,因此类型foo -> bar -> baz
与foo -> (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
开头的类型都是所谓的类型变量。类型变量可以代表任何可能的类型。因此,在上面的示例中,'
可以使用f
或int
或string
或任何内容进行调用 - 这无关紧要。
此外,如果同一类型变量多次出现在类型签名中,则它将代表相同的类型。所以在上面的例子中,这意味着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 -> c
与a -> (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
的类型。