【问题标题】:signatures/types in functional programming (OCaml)函数式编程(OCaml)中的签名/类型
【发布时间】:2010-11-17 00:15:36
【问题描述】:

我开始学习函数式编程 (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 的基础知识,但我在这里寻求帮助,因为我在 Internet 上没有找到关于 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>

感谢您的帮助和解释。

【问题讨论】:

  • 术语注释(可以帮助您进行文献搜索):Ocaml 中的“签名”通常意味着其他含义,即类型的模拟,但用于模块而不是基本表达式和值。您所询问的内容有时称为“类型签名”,但当有变量时,通常只是“类型”或“类型方案”。

标签: functional-programming ocaml signature


【解决方案1】:

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

柯里化

如你所知,如果你有 foo -&gt; bar 类型,这描述了一个函数,它接受 foo 类型的参数并返回 bar 类型的结果。由于-&gt; 是右结合的,foo -&gt; bar -&gt; baz 类型与foo -&gt; (bar -&gt; baz) 相同,因此描述了一个函数接受foo 类型的参数并返回bar -&gt; baz 类型的值,这意味着返回值是函数接受 bar 类型的值并返回 baz 类型的值。

这样的函数可以像my_function my_foo my_bar那样调用,因为函数应用是左关联的,所以和(my_function my_foo) my_bar一样,即它把my_function应用到参数my_foo,然后应用这个函数作为结果返回到参数my_bar

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

类型变量

如果你定义一个像let f x = x 这样的函数,它的类型就是'a -&gt; 'a。但是'a 实际上并不是在 OCaml 标准库的任何地方定义的类型,那么它是什么?

任何以' 开头的类型都是所谓的类型变量。类型变量可以代表任何可能的类型。所以在上面的例子中,f 可以用 intstringlist 或任何东西来调用 - 没关系。

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

所以'a -&gt; 'b -&gt; 'a 类型的函数可以接受任何类型的两个参数(第一个和第二个参数的类型可能不同)并返回与第一个参数相同类型的值,而一个函数type 'a -&gt; 'a -&gt; 'a 将接受两个相同类型的参数。

关于类型推断的一个注意事项:除非你明确地给一个函数一个类型签名,否则 OCaml 总是会推断出最通用的类​​型。因此,除非函数使用仅适用于给定类型的任何操作(例如 +),否则推断的类型将包含类型变量。

现在解释类型...

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

此类型签名告诉您something 是一个接受四个参数的函数。

第一个参数的类型是'a -&gt; 'b -&gt; 'c。 IE。一个函数,接受两个任意且可能不同类型的参数并返回任意类型的值。

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

第三个参数的类型是'a,也是两个函数的第一个参数的类型。

最后,第四个参数的类型是'd,也就是第二个函数的第二个参数的类型。

返回值将是'c类型,即第一个函数的返回类型。

【讨论】:

  • 不错的文章。看到你的版本后,我放弃了我的版本。在柯里化和类型变量之后,我还会将类型推断加入解释中。如您所知,他的第一个函数之所以说 int -> int 是因为它能够通过使用 + 运算符推断出这一点。他的其他函数不提供那种信息,所以他最终得到了类型变量。
  • @xscott:谢谢。那是个很好的观点。我在类型变量部分的最后一段中添加了一个注释。我认为添加有关类型推断的整个部分将超出此问题的范围。我认为这里重要的是理解类型的含义,而不是 ocaml 是如何提出它们的。
  • 哇,现在我知道的更多了!谢谢!但是我对这个注释有一个问题:“这里要注意的重要一点是,函数的第一个参数必须与第一个函数的第一个参数具有相同的类型,并且函数的返回值必须具有相同的类型第一个函数的第二个参数。”。为什么必须?为什么应该这样做,而不是其他方式?
  • @lever7:因为第一个函数的第一个参数是'a,第二个参数的第一个参数也是'a。如果它是'e'f 或其他我们以前从未见过的东西,它可能是任何类型。但正如我所说,如果同一个类型变量在同一个签名中出现多次,那么它每次都必须代表同一个类型。
  • @lever:或者如果您的问题是为什么该类型遵循定义:函数 f 和函数 g 都采用相同的值 (a) 作为它们的第一个参数。因此它们必须采用与第一个参数相同的类型。此外,调用g 的结果用作f 的第二个参数。所以f的第二个参数的类型必须和g返回的类型一致。
【解决方案2】:

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

另外两个提示:请注意,-> 箭头是右关联的,因此您可以将事物从右边括起来,这有时有助于理解事物,即a -&gt; b -&gt; ca -&gt; (b -&gt; c) 相同。这与第二个提示有关:高阶函数。你可以做类似的事情

let add x y = x + y
let inc = add 1

所以在 FP 中,将“添加”视为一个必须接受两个数值参数并返回一个数值的函数通常是正确的做法:它也可以是一个函数,它接受一个数字参数并返回一个类型为 num -> num 的函数。

理解这一点将帮助您理解类型签名,但您也可以不这样做。在这里,快速简单:

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

看右边。 y 有一个参数,所以它是 a -&gt; b 类型,其中 a 是 z 的类型。 x 有两个参数,第一个是z,所以第一个参数的类型也必须是a。第二个参数(y z) 的类型是b,因此x 的类型是(a -&gt; b -&gt; c)。这让您可以立即推断出s 的类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-07-05
    • 1970-01-01
    • 1970-01-01
    • 2011-06-13
    • 1970-01-01
    • 2021-01-23
    • 2012-04-14
    相关资源
    最近更新 更多