阴阳延续难题在打字语言中是否有意义?

时间:2012-02-12 16:35:21

标签: types functional-programming scheme continuations

此问题与"How the yin-yang puzzle works?"有关。根据{{​​3}}:

,阴阳计划中的延续示例如下所示
(let* ((yin
     ((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
   (yang
     ((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))))
(yin yang))

我正在尝试用(编辑:静态)类型的语言编写一段等价的代码,例如SML / NJ,但是它给了我输入错误。所以要么拼图不输入,要么我误解了方案语法。上面的代码在SML或Ocaml(扩展名为callcc)中会是什么样的?

顺便说一下,这个谜题的来源是什么?它来自哪里?

编辑:我想我知道答案。对于某些类型t,我们需要一个满足t = t -> s的递归类型s

修改编辑:不,不是,答案是递归类型t,满足t = t -> t

3 个答案:

答案 0 :(得分:7)

我想我会回答我自己的问题。我将展示两个解决方案,一个在eff中,另一个在Ocaml中。

EFF

我们将与eff合作(我在这里吹响自己的号角,请参阅下文,了解OCaml中使用Oleg's delimcc extension的其他方法。)解决方案在论文Programming with algebric effects and continuations中有所解释。

首先我们在eff中定义shiftreset

type ('a, 'b) delimited =
effect
  operation shift : (('a -> 'b) -> 'b) -> 'a
end

let rec reset d = handler
  | d#shift f k -> with reset d handle (f k) ;;

这是阴阳谜题转录为eff:

let y = new delimited in
  with reset y handle
    let yin = (fun k -> std#write "@" ; k) (y#shift (fun k -> k k)) in
    let yang = (fun k -> std#write "*" ; k) (y#shift (fun k -> k k)) in
      yin yang

但是,有人抱怨它不能解决类型方程式 α=α→β。目前,eff无法处理任意递归类型,因此我们陷入困境。作为欺骗的一种方式,我们可以关闭类型检查,看看代码是否完成了它应该做的事情:

$ eff --no-types -l yinyang.eff
@*@**@***@****@*****@******@*******@********@*********@*******...

好吧,它做的是正确的,但类型不够强大。

OCaml的

对于此示例,我们需要Oleg Kiselyov's delimcc library。代码如下:

open Delimcc ;;

let y = new_prompt () in
  push_prompt y (fun () ->
    let yin = (fun k -> print_string "@" ; k) (shift y (fun k -> k k)) in
    let yang = (fun k -> print_string "*" ; k) (shift y (fun k -> k k)) in
      yin yang)

同样,Ocaml将无法编译,因为它会触及递归类型方程。但是使用-rectypes选项,我们可以编译:

ocamlc -rectypes -o yinyang delimcc.cma yinyang.ml

按预期工作:

$ ./yinyang
@*@**@***@****@*****@******@*******@********@*********@...

OCaml计算yinyang的类型是('a -> 'a) as 'a,这是说“类型α使得α=α→α”的方式。这正是无类型λ演算模型的类型特征。所以我们有了它,阴阳拼图基本上使用无类型λ演算的特征。

答案 1 :(得分:3)

可以用C#(一种静​​态类型语言)声明一个递归函数类型:

delegate Continuation Continuation(Continuation continuation);

此定义相当于ML的α : α → α

现在我们可以将阴阳拼图“翻译”成C#。这确实需要对call / cc进行转换,我们需要进行两次转换,因为其中有两个,但结果仍然看起来非常像原始的,并且仍然有一个yin(yang)调用:

Continuation c1 = cc1 =>
{
    Continuation yin = new Continuation(arg => { Console.Write("@"); return arg; })(cc1);
    Continuation c2 = cc2 =>
    {
        Continuation yang = new Continuation(arg => { Console.Write("*"); return arg; })(cc2);
        return yin(yang);
    };
    return c2(c2);
};
c1(c1);

显然,变量yang仅在本地范围内,因此我们实际上可以对其进行优化:

Continuation c1 = cc1 =>
{
    Continuation yin = new Continuation(arg => { Console.Write("@"); return arg; })(cc1);
    Continuation c2 = cc2 => yin(new Continuation(arg => { Console.Write("*"); return arg; })(cc2));
    return c2(c2);
};
c1(c1);

现在,我们意识到那些小内联函数实际上只输出一个字符,否则什么都不做,所以我们可以打开它们:

Continuation c1 = cc1 =>
{
    Console.Write("@");
    Continuation yin = cc1;
    Continuation c2 = cc2 =>
    {
        Console.Write("*");
        return yin(cc2);
    };
    return c2(c2);
};
c1(c1);

最后,很明显变量yin也是多余的(我们可以使用cc1)。要保留原始精神,请将cc1重命名为yin,将cc2重命名为yang,然后我们将我们心爱的yin(yang)重新命名为:

Continuation c1 = yin =>
{
    Console.Write("@");
    Continuation c2 = yang =>
    {
        Console.Write("*");
        return yin(yang);
    };
    return c2(c2);
};
c1(c1);

以上所有都是相同的程序,语义上。我认为最终结果本身就是一个很棒的C#难题。所以我会回答你的问题:是的,显然即使用静态类型的语言它也很有意义:))

答案 2 :(得分:1)

另见我对how the yin yang puzzle works的回答,在我回答这个问题之前,我必须找到答案。

作为一种“打字”语言本身并不会影响这个谜题是否可以在其中表达(无论术语“打字语言”是多么模糊)。但是,最常回答你的问题:是的,这是可能的,因为Scheme本身是一种打字语言:每个值都有一个已知的类型。这显然不是你的意思,所以我假设你的意思是这是否可能在一个语言中,每个变量都被分配一个永不改变的永久类型(a.k.a。“静态类型语言”)。

此外,我假设你想要用某种语言表达时保留谜题的精神。显然,可以在x86机器代码中编写一个Scheme解释器,显然可以用类型语言编写一个x86机器代码解释器,它只有整数数据类型和函数指针。但结果却不是同一个“精神”。因此,为了使其更加精确,我将提出额外的要求:结果必须使用真正的延续来表达。不是模拟,而是真正的全面延续。

那么,你可以使用带有延续的静态类型语言吗?事实证明你可以,但你仍然可以称之为作弊。例如,在C#中,如果我的continuation被定义为“接受一个对象并返回一个对象的函数”,其中“object”是一个可以容纳任何东西的类型,你会发现这个可接受吗?如果函数接受并返回“动态”怎么办?如果我有一个“类型”语言,其中每个函数都具有相同的静态类型:“function”,而不定义参数类型和返回类型,该怎么办?结果程序是否仍然具有相同的精神,即使它使用真正的延续?

我的观点是,“静态类型”属性仍然允许类型系统中的大量变化,足以使所有差异。所以只是为了好玩,让我们考虑一下类型系统需要支持哪些类型,以便通过任何措施都有资格作为非作弊。

运算符call/cc(x)也可以写成x(get/cc),在我看来这更容易理解。在此,x是一个接受Continuation并返回值的函数,而get/cc则返回ContinuationContinuation具有函数的所有特征;它可以用一个参数调用,并且会将传入的值替换为创建它最初定位的get / cc的位置,并在此时继续执行。

这意味着get / cc有一个笨拙的类型:它是function,但是同一个位置最终会返回一个我们还不知道的类型的值。然而,假设在静态类型语言的精神中,我们需要修复返回类型。也就是说,当您调用continuation对象时,您只能传递预定义类型的值。通过这种方法,可以使用T = function T->T形式的递归表达式定义continuation函数的类型。正如朋友指出的那样,这个类型实际上可以在C#中声明:public delegate T T(T t);

所以你有它;被“打字”并不排除也不保证你可以在不改变其性质的情况下表达这个难题。但是,如果您允许静态类型“可以是任何东西”(在Java和C#中称为object),那么您需要的唯一其他内容是支持真正的延续,并且拼图可以表示没有问题。


从不同的角度来看待同样的问题,考虑我将这个谜题重写为更像传统静态类型命令式语言的东西,我在linked answer中解释过:

yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);

此处, yinyang的类型永不改变。他们始终存储“继续C,它接受C并返回C”。这与静态类型非常兼容,静态类型的唯一要求是下次执行该代码时类型不会改变。