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

发布于 2024-10-03 08:37:13 字数 1143 浏览 2 评论 0原文

我开始学习函数式编程(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>

谢谢您的帮助和解释。

I started learning functional programming (OCaml), but I don't understand one important topic about fp: signatures (I'm not sure if it's a proper name). When I type something and compile with ocaml, I get for example:

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

This is trivial, but I don't know, why this:

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

gives an output:

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

I suppose, that this topic is absolutely basics of fp for many of you, but I ask for help here, because I haven't found anything on the Internet about signatures in OCaml (there are some articles about signatures in Haskell, but not explanations).

If this topic somehow will survive, I post here several functions, which signatures made me confused:

# 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>

Thank you for help and explanation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

恬淡成诗 2024-10-10 08:37:13

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

柯里化

如您所知,如果您有以下类型foo-> bar,它描述了一个接受 foo 类型的参数并返回 bar 类型的结果的函数。由于 -> 是右关联的,因此类型 foo ->;酒吧 -> bazfoo -> 相同(bar -> baz) 从而描述了一个接受 foo 类型参数并返回 bar -> baz 类型值的函数。 baz,这意味着返回值是一个采用 bar 类型的值并返回 baz 类型的值的函数。

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

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

类型变量

如果您定义像 let fx = x 这样的函数,它将具有 'a ->; 类型。 'a.但 'a 实际上并不是 OCaml 标准库中任何地方定义的类型,那么它是什么?

任何以 ' 开头的类型都是所谓的类型变量。类型变量可以代表任何可能的类型。因此,在上面的示例中,可以使用 intstringlist 或任何其他内容来调用 f - 没关系。

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

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

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

现在解释一下类型...

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

这个类型签名告诉您,something 是一个带有四个参数的函数。

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

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

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

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

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

There are a couple of concepts you need to understand to make sense of this type signature and I don't know which ones you already do, so I tried my best to explain every important concept:

Currying

As you know, if you have the type foo -> bar, this describes a function taking an argument of type foo and returning a result of type bar. Since -> is right associative, the type foo -> bar -> baz is the same as foo -> (bar -> baz) and thus describes a function taking an argument of type foo and returning a value of type bar -> baz, which means the return value is a function taking a value of type bar and returning a value of type baz.

Such a function can be called like my_function my_foo my_bar, which because function application is left-associative, is the same as (my_function my_foo) my_bar, i.e. it applies my_function to the argument my_foo and then applies the function that is returned as a result to the argument my_bar.

Because it can be called like this, a function of type foo -> bar -> baz is often called "a function taking two arguments" and I will do so in the rest of this answer.

Type variables

If you define a function like let f x = x, it will have the type 'a -> 'a. But 'a isn't actually a type defined anywhere in the OCaml standard library, so what is it?

Any type that starts with a ' is a so-called type variable. A type variable can stand for any possible type. So in the example above f can be called with an int or a string or a list or anything at all - it doesn't matter.

Furthermore if the same type variable appears in a type signature more than once, it will stand for the same type. So in the example above that means, that the return type of f is the same as the argument type. So if f is called with an int, it returns an int. If it is called with a string, it returns a string and so on.

So a function of type 'a -> 'b -> 'a could take two arguments of any types (which might not be the same type for the first and second argument) and returns a value of the same type as the first argument, while a function of type 'a -> 'a -> 'a would take two arguments of the same type.

One note about type inference: Unless you explicitly give a function a type signature, OCaml will always infer the most general type possible for you. So unless a function uses any operations that only work with a given type (like + for example), the inferred type will contain type variables.

Now to explain the type...

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

This type signature tells you that something is a function taking four arguments.

The type of the first argument is 'a -> 'b -> 'c. I.e. a function taking two arguments of arbitrary and possibly different types and returning a value of an arbitrary type.

The type of the second argument is 'a -> 'd -> 'b. This is again a function with two arguments. The important thing to note here is that the first argument of the function must have the same type as the first argument of the first function and the return value of the function must have the same type as the second argument of the first function.

The type of the third argument is 'a, which is also the type of the first arguments of both functions.

Lastly, the type of the fourth argument is 'd, which is the type of the second argument of the second function.

The return value will be of type 'c, i.e. the return type of the first function.

撧情箌佬 2024-10-10 08:37:13

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

两个进一步的提示:请注意 ->箭头是右关联的,因此您可以将右侧的内容括起来,这有时有助于理解事物,即 a ->; b-> ca -> 相同(b -> c)。这与第二个提示有关:高阶函数。您可以

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

在 FP 中执行类似的操作,将“add”视为必须采用两个数字参数并返回一个数字值的函数通常不是正确的做法do:它也可以是一个函数,它接受一个数字参数并返回一个类型为 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。第二个参数 (yz) 的类型是 b,因此 x 的类型是 (a ->; b->c)。这使您可以立即推断出 s 的类型。

If you're really interested in the subject (and have access to a university library), read Wadler's excellent (if somewhat dated) "Introduction to functional programming". It explains type signatures and type inference in a very nice and readable way.

Two further hints: Note that the -> arrow is right-associative, so you can bracket things from the right which sometimes helps to understand things, ie a -> b -> c is the same as a -> (b -> c). This is connected to the second hint: Higher order functions. You can do things like

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

so in FP, thinking of 'add' as a function that has to take two numerical parameters and returns a numerical value is not generally the right thing to do: It can also be a function that takes one numerical argument and returns a function with type num -> num.

Understanding this will help you understand type signatures, but you can do it without. Here, quick and easy:

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

Look at the right hand side. y is given one argument, so it is of type a -> b where a is the type of z. x is given two arguments, the first one of which is z, so the type of the first argument has to be a as well. The type of (y z) , the second argument, is b, and hence the type of x is (a -> b -> c). This allows you to deduce the type of s immediately.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文