有人可以解释这个OCaml程序中使用的类型语法吗?

时间:2018-05-29 19:41:35

标签: polymorphism ocaml variant gadt

以下类型取自this question

(* 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

尝试使用int stack

制作Foo
Foo 5;;
Error: The constructor Foo expects 0 argument(s),
       but is applied here to 1 argument(s)

但是,没有参数

Foo;;
- : int stack = Foo

好的,但是int在哪里?如何以这种类型存储数据?

在下面的OP程序中,他/她匹配“正常”类型,例如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)}

有人可以帮助我填补我的知识空白吗?

2 个答案:

答案 0 :(得分:6)

这些类型是广义代数数据类型。也称为GADTs。 GADT可以改进构造函数和类型之间的关系。

在您的示例中,GADT用作引入存在量化类型的方法:删除不相关的构造函数,可以编写

type 'a task =
| Done of 'a
| AndThen : ('a -> 'b task) * 'a task -> 'b task

这里,AndThen是一个带有两个参数的构造函数:一个类型的回调 'a -> 'b task'a task并返回'b task类型的任务。此定义的一个显着特征是类型变量'a仅出现在构造函数的参数内。如果我的值为AndThen(f,t): 'a task,那么f的类型是什么?

答案是f的类型部分未知,我只知道tyf: ty -> 'a task都有t: ty类型。但是在这一点上,除ty之外的所有信息都已丢失。因此,类型ty称为存在量化类型。

但是在这里,这些小信息仍足以有意义地操纵这样的价值。我可以定义一个功能步骤

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)

如果可能,尝试在构造函数f中应用函数AndThen, 使用信息而不是构造函数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.

答案 1 :(得分:4)

我认为评论和@ octachron的回答提供了足够的细节,但我想展示我最喜欢的两个GADT力量的例子。

首先,您可以编写将返回(种类)不同类型的函数!

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表示函数接受某种类型的表达式并返回该类型的值。

# evaluate (Int 42);;
- : int = 42
# evaluate (Bool true);;
- : bool = true

显然,你无法通过简单的代数类型实现这一目标。

第二点是,GADT编译器有足够的信息来传递给你传递给函数的值,所以你可以扔掉&#34;扔掉#34;模式匹配中的一些无意义的分支:

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);;
       ^^^^^^^^
  

错误:此表达式具有int表达式          但是预期表达式为bool表达式          类型int与bool类型

不兼容

使用简单的代数类型,此示例将如下所示:

let negation = function
  | Bool b -> not b
  | Int i -> failwith "not a bool"

你不仅有一个无用的分支,而且如果你不小心通过Int 42它只会在运行时失败。