写一个班次翻译


10

编辑:你们中有些人怀疑,官方解释器中有一个错误:组成顺序.颠倒了。我有两种版本的解释器,在这里使用了错误的一种。这些示例也是针对此错误版本编写的。我已经修复了存储库中的解释器以及以下示例。的描述>也有点模棱两可,所以我已经解决了。而且,为此花了很长时间的道歉,我陷入了一些现实生活中。

EDIT2:我的解释器在实现中存在一个错误,该错误.反映在示例中(它们依赖于未定义的行为)。现在,此问题已解决。

介绍

Shift是我几年前制作的一种深奥的函数式编程语言,但今天发布了。它基于堆栈,但是像Haskell一样具有自动监视功能。

规格

Shift中有两种数据类型:

  • 功能,具有任意的正元数(输入次数),并且其返回的输出的列表。例如,一个重复其唯一输入的函数具有arity 1,而交换其两个输入的函数具有arity 2。
  • 空格完全相同,除了没有功能外没有其他目的。

Shift程序包含零个或多个命令,每个命令都是一个ASCII字符。共有8条命令:

  • !应用)弹出的功能f和值x从堆栈,并适用fx。如果f为1,则将列表f(x)添加到堆栈的最前面。如果具有arity n > 1,则将一个新的(n-1)-ary函数g推入堆栈。它接受输入并返回。x1,x2,...,xn-1f(x,x1,x2,...,xn-1)
  • ?空白)将空白推入堆栈。
  • +clone)将复制其输入的一元函数压入堆栈:任何值x都映射到[x,x]
  • >shift)将接受n-ary函数的一元函数压入堆栈f,并返回(n+1)-ary函数g,该函数忽略其第一个参数x,调用f其余参数,并x在结果前加钉。例如,shift(clone)是一个二进制函数,它接受输入a,b并返回[a,b,b]
  • /fork)将接受三个输入的三元函数压入堆栈a,b,c[b]如果a为空白,[c]则返回,否则返回。
  • $呼叫)推到堆栈弹出的功能的二元函数f和一个值x,并适用fx完全一样!一样。
  • .chain)将一个二进制函数压入堆栈,该函数会弹出两个函数fg,并返回它们的组成:h具有与ar相同的Arity 的函数,该函数f通常接受其输入f,然后将它们应用于它们,然后完全应用于g结果(调用它的次数取决于其Arity的规定),而输出中的未使用项f保留在的结果中h。例如,假设f的是,克隆其第二个参数,和一个二进制函数g呼叫。如果堆栈包含[f,g,a,b,c]并且我们包含.!!,那么它包含[chain(f,g),a,b,c]; 如果我们做!!下一个,然后f首次应用于a,b,产生[a,b,b],则将g其应用于该元素的前两个元素,因为其Arity为2,产生[a(b),b],堆栈最终为[a(b),b,c]
  • @比如)推送一元函数,该函数仅返回其输入,并0在其为空白以及1为函数的情况下进行打印。

请注意,除!简单地将一个值压入堆栈外,所有命令均无法执行输入,而输出任何内容的唯一方法是use @。通过逐一评估命令,在调用“ say”时打印0s或1s并退出来解释程序。此处未描述的任何行为(应用空格,应用长度为0或1的堆栈,在空格上调用“ chain”等)都是未定义的:解释器可能崩溃,无提示失败,要求输入或执行任何操作。

任务

您的任务是为Shift编写解释器。它应该从STDIN,命令行或函数参数中获取要解释的Shift程序,然后打印到STDOUT或返回0s和1s 的结果(可能是无限的)输出。如果编写函数,则必须能够以某种方式访问​​无限长的输出(Python中的生成器,Haskell中的惰性列表等)。另外,您也可以输入另一个数字nn如果长度大于,则返回至少一个字符n

最低字节数获胜,并且不允许出现标准漏洞。

测试用例

此Shift程序打印01

?@!@@!

从左侧开始:推一个空格,按下say,然后将say应用于该空格。这输出0。然后,将发言权推两次,然后将第二个发言权应用于第一个发言权。这输出1

该程序永远循环,不产生任何输出:

$+.!!+!!

推动callclone,然后对其应用!由于chain是二进制函数,我们需要两个s )。现在,堆栈包含一个函数,该函数接受一个参数,将其复制,然后在第二个参数上调用第一个副本。使用+!!,我们复制此函数并对其进行调用。

该程序打印0010

?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!

推空白并。然后,编写一个二进制函数,该函数复制其第二个参数b,然后复制第一个参数a并将其与自身组合,然后将该合成应用于的副本b,返回[a(a(b)),b]。其应用到和空白,然后申请比如说剩余堆栈上的两个元素。

打印此程序0。对于!!!您追加的每个内容,它都会打印出一个额外的0

?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!

推空白并。然后,组成一个三元函数,将其f,g,x作为输入并返回[f,f,g,g(x)]。克隆该函数,并将其应用于自身(例如)和空白。该应用程序不会更改堆栈,因此我们可以根据需要多次应用该函数。

此程序将打印无限序列001011011101111...,其中1s 的数量始终增加一:

@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!

存储库包含带注释的版本。


我在这里有些困惑。当您像在shift命令中一样写“ takes in”时,您是指弹出消息还是指Apply命令所施加的消息?
tecywiz121

1
另外,从您的规格来看,我真的不确定链应该如何工作。您能举个例子来澄清一下吗?
tecywiz121

@ tecywiz121这就是我的理解:假设您在堆栈的顶部有两个函数,f(x1, x2, ..., xn)g(y1, y2, ..., ym)。调用会同时.弹出它们并推送一个函数h(z1, z2, ..., zn)。现在,您可以通过逐步引入来吞噬所有这些论点!。在n这样的应用程序之后,剩余的函数只有一个参数,并在那时进行计算f(z1, z2, ..., zn)(即f应用于您输入的所有参数),这将推入一些新值,然后立即m使用堆栈中的值并对其进行调用g
马丁·恩德

@MartinBüttner如果Zgarb认为这符合规则,则可以使用第二个输入参数来定义输出的最大大小。这也将是懒惰评估问题的解决方案。
randomra 2015年

@ tecywiz121的.工作方式与Martin描述的完全相同,不同之处在于,如果f返回的列表少于m值,则结果是不确定的(该组合具有arity n,因此它不能从堆栈中获取更多参数)。本质上,的输出f用作临时堆栈,使用将其g压入和施加m时间!,并将其结果添加到主堆栈中。
Zgarb 2015年

Answers:


12

Python 2中,752 667 534 506 445 436个 427 404 398 393字节

这绝不短...但是我已经尽力了。任何打高尔夫球的建议将不胜感激。

EDIT6:现在是脚本而不是函数。将其保存到文件(shift.py,外汇)中,然后使用运行$ python shift.py '<my_input>'。请确保将输入内容放在单引号中,否则bash会因为输入字符而发疯。

EDIT7:Aaaaaaand ...不再可读。但是我又删除了23个字节,所以很好,我猜呢?我也将发布非高尔夫版本。

EDIT8:多亏了@Zgarb,多了一场高尔夫球。

k,d=[],[]
u=k.append
def z(f,a=1):f.a=a;return f
exec "i=!x:x(*map(k.pop,[-1]*x.a)));e=dict(zip('?+>/$.@',[0,!x:u(x)<u(x)),!x:u(!a,*_:x(*_)<u(a),x.a+1))),!x,y,z:u((z,y)[x<1]),3),!x,y:u(!*_:x(y,*_),x.a-1))if x.a>1 else x(y),2),!x,y:u(!*_:x(*_)<i(y),x.a)),2),!x:d.append(`+(x>0)`)<u(x))]))".replace('!',"z(lambda ")
for _ in raw_input():
 try:[i,u][_ in e](e.get(_,e['$']))
 except:break
print d

编辑:感谢@DLosc对高尔夫的帮助!设法将其减少了85个字节。

EDIT2:切掉大量不必要的包装器,再减少133个字节!

EDIT3:...还有28个感谢@ Sp3000和@orlp在聊天中!

EDIT4:在@orlp和@ Sp3000的帮助下,删除了所有装饰器,现在缩短了61个字节。

EDIT5:帮忙我,我不能停止打高尔夫球……。又多了9个字节。删除最终的print语句将节省另外7条,但是如果您在循环中运行m(),则所有输出都在同一行上...可以吗?

这是一个非高尔夫版本:

stack = []
push = stack.append

def arity(func,a=1): #give each of our functions an arity
    func.arity = a
    return func

def do(func): ##pop the args off the stack, then call the function
    args = map(stack.pop,[-1]*func.arity)
    func(*args)

def call(func,arg): #apply is just do(call)
    if func.arity == 1:
        func(arg)
    else:
        def curried(*a): #a quick little currier
            func(arg, *a)
        curried = arity(curried, func.arity - 1)
        push(curried)

def clone(arg):
    push(arg)
    push(arg)

def shift(func):
    def shifted(a, *arg):
        func(*arg)
        push(a)
    shifted = arity(shifted, func.arity + 1)
    push(shifted)

def fork(a, b, c):
    if a == 0:
        push(b)
    else:
        push(c)

def chain(func, gunc):
    def composition(*args):
        func(*args)
        do(gunc)
    composition = arity(composition, func.arity)
    push(composition)

def say(arg):
    print '10'[arg == 0],
    push(arg)

commands = {'?': 0,
            '+': arity(clone),
            '>': arity(shift),
            '/': arity(fork, 3),
            '$': arity(call, 2),
            '.': arity(chain, 2),
            '@': arity(say)}

def interpret(input_string):
    for command in input_string:
        try:
            if command == '!':
                do(call)
            else:
                push(commands[command])
        except RuntimeError: #this handles max recursion depth errors
            break            # for infinite programs
    print

if __name__ == "__main__":
    interpret(raw_input())

基本思想是python列表作为堆栈可以很好地工作,并且通过存储u=k.append,不仅可以保存字符,而且还可以@u用作装饰器来推送函数(不再适用!)。

由于作用于n-arity函数的两个函数必须能够接受任意数量的参数,因此我不得不使用*args,这意味着我原来的跟踪f.func_code.co_argcount的计划必须由Arity代替。装饰器属性。

在处理无限程序方面,解释器一直运行到达到最大递归深度为止。底部的RuntimeError处理程序此时将其安静地退出,然后打印出当前输出字符串。

测试用例:

>>> tests
['?@!@@!', '$+.!!+!!', '?@$..!!+.!!+>!.!!!!+?/!!!@!@>!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!!!!', '@?/!@>!.!!??/!!>!.!!+.!!.+>!.!!$$.!!$.!!$.!!+.!!$>!>!.!!$>!>!.!!+>!.!!$>!>!>!.!!+>!>!.!!///!!>!>!>!.!!+!!!!!']
>>> for t in tests: m(t)
0 1

0 0 1 0
0
0 0
0 0 0
0 0 0 0
0 0 1 0 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

1
我的第一个反应:@ _ @不过,认真的说,不错的工作-将实际功能放在堆栈上是一个非常整洁的解决方案。一些提示:1)三元运算符通常可以以一种或另一种方式缩短。2)您可以替换['1','0'][...]为just '10'[...]。3)为什么x is 0而不是x==0(或x<1)?4)不用麻烦指定RuntimeError,就except可以了。5)由于您使用的是Python 2,因此制表符和空格会算作不同的缩进级别,虽然很难看,但可以节省大约25个字节。
DLosc

1
您应该可以将其剪切为x.a==1and x(y)or u(a(x.a-1)(b.partial(x,y)))-逻辑运算符仍在短路,但使用的字符数比三进制数少。然后使用x.a-1条件(0 / false,如果x为1,非零/ true,否则)保存另一个字节,并交换'then'和'else'表达式:x.a-1and u(a(x.a-1)(b.partial(x,y)))or x(y)。(现在,您已经通过了我,所以我会
再打

1
在遇到与我的类似问题之后,我了解了现在的失败-如果x.a==1为真,但x(y)返回错误的结果,它也会尝试进行评估u(...)。但是看起来好像您不需要保存本来可以给您的3个字节!先生,我承认:你超越了我。
DLosc 2015年

1
只有一种说法:指定的输出格式没有空格-您可以通过各种策略来解决,而不用确定哪一个最短。当然,您的程序会处理RuntimeError一会儿我的请求,只是要求用户重定向stderr ...因此,我们甚至可能会出现疑问。; ^)
DLosc 2015年

1
什么是*_在lambda表达式?
mbomb007

4

鬼脚本

还没有打高尔夫球,因为我仍然需要计算解析功能。

此实现使用_and :代替>and /,并且它要求所有程序字符都用空格分隔。这是因为>/在Postscript中不是有效的名称,并且运算符不是自定界的,但这在我编写解析器时将得到解决。

代码的第一部分应该相当透明,因为它只是重复操作符的定义。魔术发生在的定义中!

/switch {
    /y exch def
    /x exch def
    {x} {y} ifelse
} bind def

/unwrap {
    dup type (arraytype) eq {aload pop} if
} bind def

/! { % IN: <x> <f> OUT: <g>|<f(x)>
    [ 3 1 roll unwrap] cvx %prefix argument into function
    dup /fun exch def %bind

    [ count 1 roll ] { %run the function sandboxed so it can't take any additional args
        2 dict begin
        /=only {} def % suppress output
            {
                fun
            } stopped /err exch def clear err
        end
    } .runandhide


    exch {
        $error /errorname get
        (stackunderflow) ne {
            handleerror
        } if

        $error /newerror false put

        unwrap
    } {
        unwrap exec
    } ifelse
} def

/? 0 def
/+ {{dup}} def
/_ {{/f exch def pop f}} def % using _ instead of >
/: {{? ne 3 1 roll switch}} def % using : instead of /
/$ {{!}} def
/. {{/g exch def exec g}} def 
/@ {{dup ? eq {0}{1} ifelse =only}} def

的方式!工作很简单:首先,它增加了论据x,以f通过前缀x到的内容f,推回栈上,并命名结果的副本fun

然后,将整个堆栈包装为一个数组。.runandhide是用于运行沙盒代码的Ghostscript扩展,从调用它的过程中隐藏前面数组的内容。该dict命令将新字典推到dict堆栈上,缩小了其中定义的名称范围,直到将其end弹出。它还用一个虚拟的替换=only(我在中使用的输出运算符@),从而抑制了试运行期间的输出。stopped是与try其他语言中的语句等效的PostScript ,如果其过程抛出错误,则返回true;如果运行完成,则返回false。

试运行fun完成后,程序将从隐藏数组中还原原始堆栈,如果fun完成但没有错误,则将其实际运行,并保持输出。


2

Python3,685个 670 634 633字节

我很确定这是我打过的最长的球。它曾经有点可读,但是遵循@sirpercival的建议消除了缺点!

from re import*
E,*k="E"
P="e(k.pop(),k.pop())"
def H(a,b):global k;k+=list(a)+[N(b)];exec("k+=%s;"%P*Z(N(b)));return[]
def e(a,b):a=sub("(?<!\d)0",repr(N(b,1)).replace("\\",r"\\"),a,1);return Z(a)and[a]or list(eval(a))
D=list(zip("ilhydsSNZ",[3,2,2]+[1]*6,sub("A","N(a)",',b,c:[N([b,c][a>E])]|,b:e(A,N(b))|,b:["H(%s,%s)"%(A,repr(b))]|:print(0+(a>E),end="")or[A]|:[A]*2|:["S(0,%s)"%A]|,b:b+[A]|,b=-1:sub("\d+",lambda m:str(int(m.group())+b),a)|:len(split("\D0",a))-1').split("|")))
for n,r,f in D:exec(n+"=lambda a"+f)
F=dict(zip("/$.@+>?!",D))
for z in input():n,r,f=F[z];k+=z!="!"and[[n+"(%s)"%",".join("0"*r),E][z=="?"]]or eval(P)

k是堆栈,其中包含以类似字符串的形式表示的函数"h(0,0)"(即c h ain)。当一个函数被作为参数传递给另一个函数传递,它得到repr“d和所有的数字递增:"h('h(1,1)',0)"。一旦将所有0s替换为一个函数,整个对象将传递给eval,从而调用适当的Python函数-其中大多数是从第6 exec行到第7 行的大字符串生成的lambda函数。

最大程度的麻烦是如何获得多层嵌套函数的增量,引用和正确转义。如果我可以假设函数嵌套不会进行超过9级的操作,那么我可以在正则表达式操作上节省更多,但是正如注释中指出的那样,这可能不是一个安全的假设。

未发布的早期版本的代码:

from re import *
E="E"
stack=[]

clone=lambda a:[unnest(a)]*2
shift=lambda a:["shifted(0,%s)"%unnest(a)]
fork=lambda a,b,c:[unnest(c if a!=E else b)]
call=lambda a,b:apply(unnest(a),unnest(b))
chain=lambda a,b:["chained(%s,%s)"%(unnest(a),repr(b))]
def say(a):
 print(1 if a!=E else 0,end="")
 return [unnest(a)]

shifted=lambda a,b:b+[unnest(a)]
def chained(a,b):
 global stack
 stack+=list(a)+[unnest(b)]
 exec("stack+=apply(stack.pop(),stack.pop());"*zeros(unnest(b)))
 return []

nest=lambda a,direction:sub("\d+",lambda m:str(int(m.group())+direction),a)
unnest=lambda a:nest(a,-1)
zeros=lambda a:len(split("\D0",a))-1
def apply(a,b):
 a=sub("(?<!\d)0",repr(nest(b,1)).replace("\\",r"\\"),a,1)
 return [a] if zeros(a) else list(eval(a))

functions=dict(zip("+>/$.@",zip(["clone","shift","fork","call","chain","say"],[1,1,3,2,2,1])))

for cmd in input():
 if"!"==cmd:
  stack+=apply(stack.pop(),stack.pop())
 elif"?"==cmd:
  stack+=[E]
 else:
  name,arity=functions[cmd]
  stack+=[name+"(%s)"%",".join("0"*arity)]

此实现的一个潜在缺陷是它使用了递归,因此应该无限的程序很快就会达到最大递归深度。(您可能想在运行无限程序时重定向stderr,否则堆栈跟踪将淹没实际输出。)除此之外,一切似乎都可以正常工作。


您可以编写一个程序来生成上述程序然后执行吗?您有很多重复的代码应该像lambda a和一样可压缩k.pop()
mbomb007'2015-04-22

@ mbomb007 ...我想我的大脑会爆炸。(但请参阅最近的编辑- k.pop()无论如何,我使情况的重复性降低了。)
DLosc 2015年

您能为所有这些lambda做exec / translate技巧吗?将它们全部粘在一个字符串中?
sirpercival

另一则评论:我怀疑您是否可以使用此语言嵌套<= 9
sirpercival

@sirpercival是的,我当时正在考虑尝试。不,我想不是。:^ P
DLosc 2015年

1

锡兰,1167 1057 1031

我不知道它是不是单类型的python版本...

import ceylon.language.meta.model{N=Function}import ceylon.collection{H=HashMap}interface D of F|b{}object b satisfies D{}class F(shared Integer a,[D+](D+)f,[D*]c=[])satisfies D{shared[D+]o(D i){[D+]s=[i].prepend(c);return a==1then f(*s)else[F(a-1,f,s)];}shared[D+]y([D+]i){return f(*i.prepend(c));}}F m<A>(N<[D+],A>f)given A satisfies[D+]=>F(f.parameterTypes.size,(D+i)=>f.apply(*i));[D,D]e(D x)=>[x,x];[F]t(F f){[D+]g(D+i){assert(is[D+]r=i.rest);return[i[0],*f.y(r)];}return[F(f.a+1,g)];}[D]k(D a,D d,D c)=>a==b then[d]else[c];[D+]l(F a,D x)=>a.o(x);[F]n(F f,F g){[D+]h(D+i){[D+]r=f.y(i);assert(is[D+]d=r[0:g.a]);return g.y(d).append(r[g.a...]);}return[F(f.a,h)];}[D]y(D x){process.write(x==b then"0"else"1");return[x];}class I(){variable D[]s=[];value c=H{'?'->b,'+'->m(`e`),'>'->m(`t`),'/'->m(`k`),'$'->m(`l`),'.'->m(`n`),'@'->m(`y`)};shared void r(Character i){if(i=='!'){assert(is F f=s[0],is D x=s[1]);s=f.o(x).append(s[2...]);}else{assert(is D d=c[i]);s=[d].append(s);}}}shared void z(){process.readLine()?.collect(I().r);}

这是同一代码的格式化(和注释)版本(带有空格/换行符/注释,变为4867字节):

import ceylon.language.meta.model {
    N=Function
}
import ceylon.collection {
    H=HashMap
}
//↑ Import of stuff we need – with a shorter alias.
// (The comment is down here due to a bug in my comment and space
//  remover – it doesn't remove a comment if it is the first token
//  at all.)

// Our data items are either functions or blanks.
interface D of F | b {}

// There is no point in having many blanks – so here a singleton.
object b satisfies D {}

// The function class. Our functions take a number of data items,
// and return a number of data items.
// We know the arity a, and have also an actual function f, and a number
// or already collected arguments.
class F(shared Integer a, [D+](D+) f, [D*] c = [])
        satisfies D {
    // apply once (= collect one parameter). Returns either the result,
    // or a function with arity one less.
    shared [D+] o(D i) {
        [D+] s = [i].prepend(c);
        return a == 1 then f(*s) else [F(a - 1, f, s)];
    }
    // apply fully (= with all needed parameters).
    // The input size should equal the arity.
    shared [D+] y([D+] i) {
        // merge collected and input arguments.
        return f(*i.prepend(c));
    }
}
// creates a shift function from a ceylon function,
// deriving the arity using reflection.
F m<A>(N<[D+],A> f)
        given A satisfies [D+]
        => F(f.parameterTypes.size, (D+ i) => f.apply(*i));

//
// clone: a unary function that duplicates its input: any value x is mapped to [x,x].
//
[D, D] e(D x) => [x, x];

//
// shift: a unary function that takes in an n-ary function f, and returns an
// (n+1)-ary function g that ignores its first argument x, calls f on the
// remaining ones, and tacks x in front of the result. For example,
// shift(clone) is a binary function that takes inputs a,b and returns [a,b,b].
//
[F] t(F f) {
    [D+] g(D+ i) {
        assert (is [D+] r = i.rest);
        return [i[0], *f.y(r)];
    }
    return [F(f.a + 1, g)];
}

//
// fork: a ternary function that takes three inputs a,d,c, and returns [d] if a is a blank,
// and [c] otherwise.
//
[D] k(D a, D d, D c) => a == b then [d] else [c];

//
// call: a binary function that pops a function f and a value x,
//        and applies f to x exactly as ! does.
//
[D+] l(F a, D x) => a.o(x);

//
// chain:  a binary function that pops two functions f and g, and returns their composition:
//         a function h that has the same arity as f, and which takes its inputs normally, applies
//         f to them, and then fully applies g to the result (calls it as many times as its arity
//         dictates), with unused items from the output of f remaining in the result of h. For
//         example, suppose that f is a binary function that clones its second argument, and
//         g is call. If the stack contains [f,g,a,b,c] and we do .!!, then it contains
//         [chain(f,g),a,b,c]; if we do !! next, then f is first applied to a,b, producing
//         [a,b,b], then g is applied to the first two elements of that since its arity is 2,
//         producing [a(b),b], and the stack will finally be [a(b),b,c].
//
[F] n(F f, F g) {
    [D+] h(D+ i) {
        // call f, remember the results.
        [D+] r = f.y(i);
        // first some results from f are the arguments to g:
        assert (is [D+] d = r[0:g.a]);
        // remaining results from f are passed back directly, with the results from g.
        return g.y(d).append(r[g.a...]);
    }
    return [F(f.a, h)];
}

//
// say: a unary function that simply returns its input, and prints 0 if it was a blank,
//      and 1 if it was a function.
// 
[D] y(D x) {
    process.write(x == b then "0" else "1");
    return [x];
}

//
// Interpreter class, which manages the stack and interprets the commands.
// Just call the r method with the individual command characters.
//
class I() {
    // The stack. The only variable in the whole program.
    variable D[] s = [];

    // a hash map of items to be pushed by commands, most build using the m function.
    // The apply command is not here, this is handled separately by the interpreter. 
    value c = H {
        '?'->b,
        '+'->m(`e`),
        '>'->m(`t`),
        '/'->m(`k`),
        '$'->m(`l`),
        '.'->m(`n`),
        '@'->m(`y`)
    };

    // Interprets one command, indicated by a character.
    // Will throw an AssertionError for unknown commands.
    shared void r(Character i) {
        if (i == '!') {
            assert (
                is F f = s[0],
                is D x = s[1]);
            // apply f on x, push the result onto a shortened version of the stack.
            s = f.o(x).append(s[2...]);
        } else {
            assert (is D d = c[i]);
            // push d on top of the stack.
            s = [d].append(s);
        }
    }
}

shared void z() {
    process.readLine()?.collect(I().r);
}

函数clone e,shift t,fork k,call l,say y和chain n使用缩写版本名称的最后一个字母,因为这样可以减少冲突。(花絮:叉最初定义是这样的:[Data] fork(Data a, Data b, Data c) => a == blank then [b] else [c];-当我改名blankb,这打破了,因为现在比较参数a,并b改为a与空白我花了一些时间来调试。)

z功能是共享的,因为我的IDE运行这些功能-命令行工具也可以运行非共享的功能。


实际上,循环版本将在某个时候引发StackOverflowError,然后完成。JVM没有递归堆栈优化(或至少没有一个适合我的程序的优化)。
圣保罗Ebermann
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.