此问题与"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
。
答案 0 :(得分:7)
我想我会回答我自己的问题。我将展示两个解决方案,一个在eff中,另一个在Ocaml中。
我们将与eff合作(我在这里吹响自己的号角,请参阅下文,了解OCaml中使用Oleg's delimcc extension的其他方法。)解决方案在论文Programming with algebric effects and continuations中有所解释。
首先我们在eff中定义shift
和reset
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
@*@**@***@****@*****@******@*******@********@*********@*******...
好吧,它做的是正确的,但类型不够强大。
对于此示例,我们需要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计算yin
和yang
的类型是('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
则返回Continuation
。 Continuation
具有函数的所有特征;它可以用一个参数调用,并且会将传入的值替换为创建它最初定位的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);
此处, yin
和yang
的类型永不改变。他们始终存储“继续C,它接受C并返回C”。这与静态类型非常兼容,静态类型的唯一要求是下次执行该代码时类型不会改变。