有人可以解释这个 OCaml 程序中使用的类型语法吗?
Can someone explain the type syntax used in this OCaml program?
以下类型取自
(* contains an error, later fixed by the OP *)
type _ task =
| Success : 'a -> 'a task
| Fail : 'a -> 'a task
| Binding : (('a task -> unit) -> unit) -> 'a task
| AndThen : ('a -> 'b task) * 'a task -> 'b task
| OnError : ('a -> 'b task) * 'a task -> 'b task
type _ stack =
| NoStack : 'a stack
| AndThenStack : ('a -> 'b task) * 'b stack -> 'a stack
| OnErrorStack : ('a -> 'b task) * 'b stack -> 'a stack
type 'a process =
{ root: 'a task
; stack: 'a stack
}
我对 OCaml 比较陌生,但我以前从未见过 :
语法是这样使用的。例如,我见过这样定义的多态类型,使用 of
语法
type 'a expr =
| Base of 'a
| Const of bool
| And of 'a expr list
| Or of 'a expr list
| Not of 'a expr
在最初的问题中,变体是如何构造的对我来说并不明显,因为看起来每个变体都不接受参数。以这个简化的例子为例
type 'a stack =
| Foo : int stack
| Bar : string stack
;;
type 'a stack = Foo : int stack | Bar : string stack
尝试使用 Foo
制作 int stack
Foo 5;;
Error: The constructor Foo expects 0 argument(s),
but is applied here to 1 argument(s)
然而,没有争论
Foo;;
- : int stack = Foo
好的,但是 int
在哪里?如何存储这种类型的数据?
在下面的 OP 程序中,he/she 匹配类型 "normally",例如 Success value -> ...
或 Fail value -> ...
。同样,如果变体构造函数不接受参数,这个值是如何构造的?
let rec loop : 'a. 'a process -> unit = fun proc ->
match proc.root with
| Success value ->
let rec step = function
| NoStack -> ()
| AndThenStack (callback, rest) -> loop {proc with root = callback value; stack = rest }
| OnErrorStack (_callback, rest) -> step rest <-- ERROR HERE
in
step proc.stack
| Fail value ->
let rec step = function
| NoStack -> ()
| AndThenStack (_callback, rest) -> step rest
| OnErrorStack (callback, rest) -> loop {proc with root = callback value; stack = rest }
in
step proc.stack
| Binding callback -> callback (fun task -> loop {proc with root = task} )
| AndThen (callback, task) -> loop {root = task; stack = AndThenStack (callback, proc.stack)}
| OnError (callback, task) -> loop {root = task; stack = OnErrorStack (callback, proc.stack)}
有人可以帮我填补我的知识空白吗?
这些类型是广义代数数据类型。也称为 GADTs 。 GADT 使细化构造函数和类型之间的关系成为可能。
在您的示例中,GADT 用作引入存在量化类型的一种方式:删除不相关的构造函数,可以这样写
type 'a task =
| Done of 'a
| AndThen : ('a -> 'b task) * 'a task -> 'b task
这里,AndThen
是一个有两个参数的构造函数:一个回调类型
'a -> 'b task
和 'a task
和 returns 类型 'b task
的任务。这个定义的一个显着特征是类型变量 'a
只出现在构造函数的参数中。那么一个自然的问题是,如果我有一个值 AndThen(f,t): 'a task
,f
的类型是什么?
而答案是 f
的类型是部分未知的,我只知道有一个类型 ty
使得 f: ty -> 'a task
和 t: ty
。但在这一点上,除了 ty
的存在之外的所有信息都已丢失。因此,类型 ty
被称为存在量化类型。
但是在这里,这个小信息仍然足以有意义地操纵这样的值。我可以定义一个函数 step
let rec step: type a. a task -> a task = function
| Done _ as x -> x
| AndThen(f,Done x) -> f x
| AndThen(f, t) -> AndThen(f, step t)
如果可能,它会尝试在构造函数 AndThen
中应用函数 f
,
使用信息比构造函数 AndThen
总是存储兼容的回调和任务对。
例如
let x: int task = Done 0
let f: int -> float task = fun x -> Done (float_of_int (x + 1))
let y: float task = AndThen(f,x)
;; step y = Done 1.
我认为评论和@octachron 的回答提供了足够的细节,但我想展示我最喜欢的两个 GADT 功能示例。
首先,您可以编写 return(种)不同类型的函数!
type _ expression = Int : int -> int expression
| Bool : bool -> bool expression
let evaluate : type t. t expression -> t = function
| Int i -> i
| Bool b -> b
type t. t expression -> t
表示该函数接受某种类型的表达式和 return 该类型的值。
# evaluate (Int 42);;
- : int = 42
# evaluate (Bool true);;
- : bool = true
很明显,您无法使用简单的代数类型实现此目的。
第二点是 GADTs 编译器有足够的关于你传递给函数的值的信息,所以你可以 "throw away" 模式匹配中的一些无意义的分支:
let negation : bool expression -> bool = function
| Bool b -> not b
因为 bool expression -> bool
OCaml 知道 negation
只适用于 bool
所以没有关于缺少 Int i
分支的警告。如果您尝试使用 Int i
调用它,您将看到类型错误:
# negation (Int 42);;
Characters 9-17:
negation (Int 42);;
^^^^^^^^
Error: This expression has type int expression
but an expression was expected of type bool expression
Type int is not compatible with type bool
对于简单的代数类型,此示例将如下所示:
let negation = function
| Bool b -> not b
| Int i -> failwith "not a bool"
你不仅有一个无用的分支,而且如果你不小心通过 Int 42
它只会在运行时失败。
以下类型取自
(* contains an error, later fixed by the OP *)
type _ task =
| Success : 'a -> 'a task
| Fail : 'a -> 'a task
| Binding : (('a task -> unit) -> unit) -> 'a task
| AndThen : ('a -> 'b task) * 'a task -> 'b task
| OnError : ('a -> 'b task) * 'a task -> 'b task
type _ stack =
| NoStack : 'a stack
| AndThenStack : ('a -> 'b task) * 'b stack -> 'a stack
| OnErrorStack : ('a -> 'b task) * 'b stack -> 'a stack
type 'a process =
{ root: 'a task
; stack: 'a stack
}
我对 OCaml 比较陌生,但我以前从未见过 :
语法是这样使用的。例如,我见过这样定义的多态类型,使用 of
语法
type 'a expr =
| Base of 'a
| Const of bool
| And of 'a expr list
| Or of 'a expr list
| Not of 'a expr
在最初的问题中,变体是如何构造的对我来说并不明显,因为看起来每个变体都不接受参数。以这个简化的例子为例
type 'a stack =
| Foo : int stack
| Bar : string stack
;;
type 'a stack = Foo : int stack | Bar : string stack
尝试使用 Foo
int stack
Foo 5;;
Error: The constructor Foo expects 0 argument(s),
but is applied here to 1 argument(s)
然而,没有争论
Foo;;
- : int stack = Foo
好的,但是 int
在哪里?如何存储这种类型的数据?
在下面的 OP 程序中,he/she 匹配类型 "normally",例如 Success value -> ...
或 Fail value -> ...
。同样,如果变体构造函数不接受参数,这个值是如何构造的?
let rec loop : 'a. 'a process -> unit = fun proc ->
match proc.root with
| Success value ->
let rec step = function
| NoStack -> ()
| AndThenStack (callback, rest) -> loop {proc with root = callback value; stack = rest }
| OnErrorStack (_callback, rest) -> step rest <-- ERROR HERE
in
step proc.stack
| Fail value ->
let rec step = function
| NoStack -> ()
| AndThenStack (_callback, rest) -> step rest
| OnErrorStack (callback, rest) -> loop {proc with root = callback value; stack = rest }
in
step proc.stack
| Binding callback -> callback (fun task -> loop {proc with root = task} )
| AndThen (callback, task) -> loop {root = task; stack = AndThenStack (callback, proc.stack)}
| OnError (callback, task) -> loop {root = task; stack = OnErrorStack (callback, proc.stack)}
有人可以帮我填补我的知识空白吗?
这些类型是广义代数数据类型。也称为 GADTs 。 GADT 使细化构造函数和类型之间的关系成为可能。
在您的示例中,GADT 用作引入存在量化类型的一种方式:删除不相关的构造函数,可以这样写
type 'a task =
| Done of 'a
| AndThen : ('a -> 'b task) * 'a task -> 'b task
这里,AndThen
是一个有两个参数的构造函数:一个回调类型
'a -> 'b task
和 'a task
和 returns 类型 'b task
的任务。这个定义的一个显着特征是类型变量 'a
只出现在构造函数的参数中。那么一个自然的问题是,如果我有一个值 AndThen(f,t): 'a task
,f
的类型是什么?
而答案是 f
的类型是部分未知的,我只知道有一个类型 ty
使得 f: ty -> 'a task
和 t: ty
。但在这一点上,除了 ty
的存在之外的所有信息都已丢失。因此,类型 ty
被称为存在量化类型。
但是在这里,这个小信息仍然足以有意义地操纵这样的值。我可以定义一个函数 step
let rec step: type a. a task -> a task = function
| Done _ as x -> x
| AndThen(f,Done x) -> f x
| AndThen(f, t) -> AndThen(f, step t)
如果可能,它会尝试在构造函数 AndThen
中应用函数 f
,
使用信息比构造函数 AndThen
总是存储兼容的回调和任务对。
例如
let x: int task = Done 0
let f: int -> float task = fun x -> Done (float_of_int (x + 1))
let y: float task = AndThen(f,x)
;; step y = Done 1.
我认为评论和@octachron 的回答提供了足够的细节,但我想展示我最喜欢的两个 GADT 功能示例。
首先,您可以编写 return(种)不同类型的函数!
type _ expression = Int : int -> int expression
| Bool : bool -> bool expression
let evaluate : type t. t expression -> t = function
| Int i -> i
| Bool b -> b
type t. t expression -> t
表示该函数接受某种类型的表达式和 return 该类型的值。
# evaluate (Int 42);;
- : int = 42
# evaluate (Bool true);;
- : bool = true
很明显,您无法使用简单的代数类型实现此目的。
第二点是 GADTs 编译器有足够的关于你传递给函数的值的信息,所以你可以 "throw away" 模式匹配中的一些无意义的分支:
let negation : bool expression -> bool = function
| Bool b -> not b
因为 bool expression -> bool
OCaml 知道 negation
只适用于 bool
所以没有关于缺少 Int i
分支的警告。如果您尝试使用 Int i
调用它,您将看到类型错误:
# negation (Int 42);;
Characters 9-17:
negation (Int 42);;
^^^^^^^^
Error: This expression has type int expression but an expression was expected of type bool expression Type int is not compatible with type bool
对于简单的代数类型,此示例将如下所示:
let negation = function
| Bool b -> not b
| Int i -> failwith "not a bool"
你不仅有一个无用的分支,而且如果你不小心通过 Int 42
它只会在运行时失败。