OCaml的类型系统是否阻止它对教堂数字进行建模?

时间:2017-04-15 13:36:09

标签: ocaml lambda-calculus church-encoding

作为一个传递时间,我正在努力实现我在大学学习的课程(涉及Lambda微积分和各种编程概念)中提出的各种问题。所以,我正在尝试在OCaml中实现教会数字和相关运算符(也作为OCaml中的练习)。

这是到目前为止的代码:

let church_to_int n =
    n (fun x -> x + 1) 0;;

let succ n s z =
    s (n s z)

let zero = fun s z -> z

let int_to_church i =
    let rec compounder i cont =
        match i with
        | 0 -> cont zero
        | _ -> compounder (i - 1) (fun res -> cont (succ res))
    in
    compounder i (fun x -> x)

let add a b = (b succ) a

let mul a b = (b (add a)) zero

所以,它似乎有效,但随后就崩溃了。让我们考虑一下这些定义:

let three = int_to_church 3
let four = int_to_church 4
church_to_int (add three four) // evaluates to 7
church_to_int (add four three) // throws type error - see later

我得到的错误与教会数字的类型多态性在定义时有关(参见SO question),然后在调用一次闭包后解析。但是,我似乎不明白为什么在这种情况下抛出类型不一致错误:

let three = int_to_church 3
let four = int_to_church 4
church_to_int (mul three four) // throws type error - see later

有什么想法吗?

具体错误:

1

Error: This expression has type (int -> int) -> int -> int                                                 but an expression was expected of type                                                                ((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) ->                                    
         ((((int -> int) -> int -> int) -> (int -> int) -> int -> int) ->
          ((int -> int) -> int -> int) -> (int -> int) -> int -> int) ->
         'd
       Type int is not compatible with type ('a -> 'b) -> 'c -> 'a 

2

Error: This expression has type                                                                              ((((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) ->                                             (('d -> 'd) -> 'd -> 'd) -> 'e) ->                                                       
          ((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) ->
          (('d -> 'd) -> 'd -> 'd) -> 'e) ->
         (((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) ->
          (('d -> 'd) -> 'd -> 'd) -> 'e) ->
         ((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) ->
         (('d -> 'd) -> 'd -> 'd) -> 'e
       but an expression was expected of type
         ((((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) ->
           (('d -> 'd) -> 'd -> 'd) -> 'e) ->
          'e) ->
         ('f -> 'g -> 'g) -> 'h
       The type variable 'e occurs inside
       ((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) ->
       (('d -> 'd) -> 'd -> 'd) -> 'e

2 个答案:

答案 0 :(得分:2)

如果你看一下三种类型,你就明白了:

val three : ('_a -> '_a) -> '_a -> '_a = <fun> 

这只是工作中的价值限制。这里很好地解释了价值限制:https://realworldocaml.org/v1/en/html/imperative-programming-1.html#side-effects-and-weak-polymorphism

要解决这个问题,在这种情况下,您只需简单地扩展功能:

let three x = int_to_church 3 x ;;
val three : ('a -> 'a) -> 'a -> 'a = <fun> 

答案 1 :(得分:1)

我对lambda演算有点生疏,但在与一些聪明的老人讨论之后,我得到了这个答案:我明确地喜欢你带来的问题,是的,这样写,OCaml的类型系统不允许写出教会数字。

这里真正的问题是你的加法术语:

tuples = [(0, None, 1492333402, (b'sendgrid.net',)),
(0, None, 1492333401, (b'internal-itsmlb-1716930720.eu-west-1.elb.amazonaws.com',)),
(0, None, 1492331992, (b'internal-prolb-1409411760.eu-west-1.elb.amazonaws.com',)),
(0, None, 1492331992, (b'internal-hodor-lb-498086888.eu-west-1.elb.amazonaws.com',)),
(0, None, 1492333397, (b'edge.ssl.deals.souq.com',))]
result = [t[3][0].decode() for t in tuples]

具有以下类型

let add a b = b succ a

add的参数类型不同。这有点令人伤心,因为我们天真地期望加法是可交换的。 您可以在写作时轻松验证:

(((('a -> 'b) -> 'c -> 'a) -> ('a -> 'b) -> 'c -> 'b) -> 'd -> 'e) -> 'd -> 'e

这个错误意味着你试图将一个类型统一为“更大”的类型,即包含它的类型(例如:用'a - &gt;'a 统一'a')。 OCaml不允许这样做(除非你设置-rectypes选项允许循环类型)。 为了更好地理解正在发生的事情,让我们添加类型注释来帮助改变它(为了清晰起见,我会改变你的注释):

let double a = add a a //produces type error - the type variable occurs ...

现在让我们回到添加这个术语并稍微注释一下,看看typer有什么要说的:

type 'a church = ('a -> 'a) -> 'a -> 'a
let zero : 'a church = fun f x -> x
let succ n : 'a church = fun f x -> f (n f x)

这会产生一种非常奇怪的类型:

let add (a:'a church) b = a succ b // we only annotate "a" to let the typer infer the rest

这很有趣:为什么第一个arg键入'教堂教会

答案如下:这里,教堂整数是一个可以浏览空间的移动功能('a - &gt;'a )的值,以及属于该空间的起点'a )。

在这里,如果我们指定参数 a 的类型'是一个教会,那么'代表我们可以移动的空间。 由于 succ a 移动功能在教会上运行,而不是'a 这里是自我'教会,从而使参数 a 成为'教会教会

这完全不是我们在开始时想要的类型......但这证明了为什么类型系统不允许你的值作为第一个并且添加的参数。

一种解决方案可以是以不同的方式编写添加:

'a church church -> 'a church -> 'a church

这里,a和b都具有相同的移动功能,因此是相同的类型,但你不再享受部分应用程序的漂亮写作......

另一个让你保持这篇美丽写作的解决方案是使用通用类型,允许更大类的多态:

let add a b f x = a f (b f x)

但是这个解决方案比你最初的解决方案更冗长。

希望很清楚。