types - 具有差异列表的 Nat 类型

标签 types ocaml gadt

我将自然数编码为差异列表:

type z = Z
type +'a s = S

type _ nat =
  | Z : ('l * 'l) nat
  | S : ('l * 'm) nat -> ('l * 'm s) nat

这让我可以轻松地对加法进行编码:

let rec add : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
  fun i1 i2 ->  match i2 with
    | Z -> i1
    | S i -> S (add i1 i) (* OK *)

但是以下变体不进行类型检查:

let rec add2 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
  fun i1 i2 ->  match i2 with
    | Z -> i1
    | S i -> add2 (S i1) i    (* KO *)

有没有人知道如何让它正确,即有一个高效的类型添加,可能改变 Nat 类型?

请注意,这个问题比 Nat 加法更普遍:对于更复杂的类型,同样的问题也会出现。例如。使用大小列表,所有基于累加器的函数(如 rev_append)都很难输入。

最佳答案

这里的问题是 S i1 的类型是 (l s * m s) nati 的类型是 m * n。因此,对 add2 的递归调用是错误类型的:add2 期望其第一个参数的最右边索引与其第二个参数的最左边索引匹配,并且 m s 绝对不同于 m

因为您将一个值编码为差异,所以您可以轻松解决这个问题:您可以注意到 (l * m) nat(l s * m s) nat应该是一样的东西。你确实可以定义一个函数 shift 将一个转换为另一个,这基本上是一个恒等函数:

let rec shift : type l m. (l*m) nat -> (l s * m s) nat = function
  | Z   -> Z
  | S i -> S (shift i)

然后,您可以在对 add2 的递归调用中调整 i 的类型,以对整个内容进行类型检查:

let rec add2 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
  fun i1 i2 ->  match i2 with
    | Z -> i1
    | S i -> add2 (S i1) (shift i)

编辑:摆脱非尾递归 shift

继续传递样式转换

将非尾递归函数转换为尾递归函数的常用技术是在 Continuation Passing Style 中定义它。 : 该函数带有一个额外的参数,该参数描述了如何处理返回值

我们可以将 shift 变成尾递归函数 shift3 ,如下所示:

let shift3 : type l m. (l*m) nat -> (l s * m s) nat =
  let rec aux : type l m c. ((l s * m s) nat -> c) -> (l * m) nat -> c =
   fun k i -> match i with
     | Z -> k Z
     | S j -> aux (fun i -> k (S i)) j
  in fun i -> aux (fun x -> x) i

然后让我们定义 add3:

let rec add3 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
  fun i1 i2 ->  match i2 with
    | Z -> i1
    | S i -> add3 (S i1) (shift3 i)

大锤:Obj.magic

摆脱非尾递归 shift 函数的一种(简单但肮脏的)方法是用 Obj.magic 替换它:确实,如前所述,我们的 shift 只不过是结构定义的恒等函数。

这导致我们:

let shift4 : type l m. (l*m) nat -> (l s * m s) nat = Obj.magic

let rec add4 : type l m n . (l*m) nat -> (m*n) nat -> (l*n) nat =
  fun i1 i2 ->  match i2 with
    | Z -> i1
    | S i -> add4 (S i1) (shift4 i)

关于types - 具有差异列表的 Nat 类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34092440/

相关文章:

scala - 我可以使用绑定(bind)在 Scala 抽象方法上的类型,然后使用 "tighten up"子类中的定义吗?

c++ - 从奇怪的模板类型转换

python - 具有一个虚假元素的 numpy 数组的真值似乎取决于 dtype

haskell - 如何为此 GADT 编写序列化实例?

c++ - C++ 中的标量类型附加

c - 尽管轮询文件描述符,但不可靠的 http 客户端

ocaml - 元奥Caml : Can't run code from brackets

ocaml - OCaml 的 -ppx 语法扩展的状态如何?

haskell - 隐藏 State monad 的类型参数

haskell - 删除类型参数的 GADT 的相等性