シフト通訳を書く


10

編集:あなたの一部が疑ったように、公式インタープリターにバグがありました:の構成の順序.が逆になりました。私は2つのバージョンのインタープリターを持っていて、ここでは間違ったバージョンを使用しました。例は、この誤ったバージョン用にも書かれています。リポジトリのインタープリターと以下の例を修正しました。の説明>も少しあいまいだったので修正しました。また、時間がかかることをお詫びし、実生活に巻き込まれました。

EDIT2:私のインタプリタの実装にはバグがあり.、例に反映されていました(未定義の動作に依存していました)。この問題は修正されました。

前書き

Shiftは、数年前に作成した難解な関数型プログラミング言語ですが、今日公開されました。スタックベースですが、Haskellのような自動カレーも備えています。

仕様

Shiftには2つのデータ型があります。

  • 任意の正のアリティ(入力数)を持ち、出力のリストを返す関数。たとえば、唯一の入力を複製する関数にはアリティ1があり、2つの入力を交換する関数にはアリティ2があります。
  • ブランクはすべて同一であり、機能以外の目的はありません。

シフトプログラムは0個以上のコマンドで構成され、各コマンドは単一のASCII文字です。合計で8つのコマンドがあります。

  • !適用)スタックから関数fと値をポップしx、に適用fxます。場合はfアリティ1を持って、リストにはf(x)、スタックの先頭に追加されます。アリティがあるn > 1場合、新しい(n-1)-ary関数gがスタックにプッシュされます。入力を受け取り、戻ります。x1,x2,...,xn-1f(x,x1,x2,...,xn-1)
  • ?blank)スタックに空白をプッシュします。
  • +clone)は、入力を複製する単項関数をスタックにプッシュします。任意の値xがにマップされ[x,x]ます。
  • >shift)はn-ary関数を受け取る単項関数をスタックにプッシュし、最初の引数を無視し、残りの引数を呼び出し、結果の前にタックfする(n+1)-ary関数gを返します。たとえば、は入力を受け取ってを返すバイナリ関数です。xfxshift(clone)a,b[a,b,b]
  • /fork)は、3つの入力を受け取る3項関数をスタックにプッシュし、が空白の場合はそれ以外a,b,cを返します。[b]a[c]
  • $呼び出し)は、関数fと値をポップするバイナリ関数をスタックにプッシュし、そうxするのfxまったく同じように適用され!ます。
  • .)スタックにプッシュ二つの機能をポップバイナリ関数fgし、その組成を返す:関数hと同じアリティを有しf、通常はその入力をとるには、適用されるfそれらにし、次いで完全適用g結果に(コールそのアリティが指示する回数だけ)、の出力からの未使用のアイテムはの結果にf残りhます。たとえば、それfが2番目の引数を複製するバイナリ関数で、gcallであるとします。スタックが含まれている場合は[f,g,a,b,c]、私たちが行う.!!、それが含まれています[chain(f,g),a,b,c]!!次に行う場合、f最初にに適用されa,b[a,b,b]、それからgその最初の2つの要素に適用され[a(b),b]ます[a(b),b,c]。そのアリティは2であるため、生成し、スタックは最終的にになります。
  • @言う)単純に入力を返す単項関数をプッシュし、0それが空白1か、関数かを出力します。

!値をスタックにプッシュする以外のすべてのコマンドは、入力を実行する方法がなく、何かを出力する唯一の方法はを使用すること@です。プログラムは、コマンドを1つずつ評価し、「say」が呼び出されるたびに0sまたは1sを出力して終了することによって解釈されます。ここで説明されていない動作(空白の適用、長さ0または1のスタックの適用、空白での「チェーン」の呼び出しなど)は定義されていません。インタープリターがクラッシュしたり、警告なしに失敗したり、入力を要求したりする場合があります。

タスク

あなたの仕事はシフトの通訳を書くことです。STDIN、コマンドライン、または関数の引数から解釈されるShiftプログラムを取得し、STDOUTに出力するか、0sおよび1sの結果の出力(おそらく無限)を返す必要があります。関数を作成する場合、何らかの方法(Pythonのジェネレーター、Haskellの遅延リストなど)で無限長の出力にアクセスできる必要あります。または、別の入力である数値を受け取り、それよりも長い場合はn少なくともn出力の文字を返すことができますn

最も低いバイト数が優先され、標準の抜け穴は許可されません。

テストケース

このシフトプログラムは次のように出力します01

?@!@@!

左から:空白を押し、「発言」を押しから、発言を空白に適用します。これは出力します0。次に、sayを 2回押し、2番目の発言を最初の発言に適用します。これは出力します1

このプログラムは永久にループし、出力を生成しません。

$+.!!+!!

プッシュ呼び出しクローンを作成し、それらにチェーンを適用します(チェーンはバイナリ関数である!ため、2つのが必要です)。これで、スタックには1つの引数を取り、それを複製して、2番目の最初のコピーを呼び出す関数が含まれています。では、この関数を複製してそれ自体を呼び出します。+!!

このプログラムは印刷します0010

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

空白を押して言ってください。次に、2番目の引数をコピーするバイナリ関数を作成してからb、最初の引数をコピーしaてそれ自体で作成し、次にそのコピーをのコピーに適用してb、を返し[a(a(b)),b]ます。適用言うと、ブランク、その後、適用発言権をスタック上に残りの二つの要素に。

このプログラムは印刷し0ます。!!!追加したそれぞれについて、追加のを出力し0ます。

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

空白を押して言ってください。次に、f,g,x入力としてを返す3項関数を作成します[f,f,g,g(x)]。その関数のクローンを作成し、それ自体、たとえば、および空白に適用します。このアプリケーションはスタックを変更しないため、必要な回数だけ関数を再度適用できます。

このプログラムは無限シーケンスを出力します。s 001011011101111...の数は1常に1ずつ増加します。

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

リポジトリには注釈付きのバージョンが含まれています。


ここでは少し混乱しています。あなたがシフトコマンドのように「テイクイン」を書くとき、あなたはポップを意味しますか、それとも、適用コマンドによって適用されることを意味しますか?
tecywiz121 2015

1
また、私はあなたの仕様からチェーンがどのように機能することになっているのか本当にわかりません。例を挙げて説明してください。
tecywiz121 2015

@ tecywiz121これが私が理解している方法です。スタックの一番上に2つの関数があるf(x1, x2, ..., xn)としg(y1, y2, ..., ym)ます。呼び出す.と、両方がポップされ、関数がプッシュされますh(z1, z2, ..., zn)。これで、で徐々にカレー化することにより、これらすべての議論を食い尽くすことができます!。このnようなアプリケーションの後、残りの関数には引数が1つしかなく、その時点で計算f(z1, z2, ..., zn)(つまりf、カリー化したすべての引数に適用)が行われ、いくつかの新しい値がプッシュされ、すぐmにスタックから値が消費さgれて呼び出されます。
マーティンエンダー

@MartinBüttnerZgarbがルールに従っていると判断した場合は、出力の最大サイズを定義する2番目の入力パラメーターを使用できます。これは、遅延評価の問題の解決策にもなります。
randomra 2015

@ tecywiz121 .は、Martinの説明とまったく同じように機能しfます。ただし、m値よりも小さいリストを返す場合、結果は未定義です(コンポジションにはarity nがあるため、スタックからさらに引数を食べることはできません)。本質的に、出力fれた一時的なスタックとして使用され、g押されて印加されるm時間は、使用!、及びその結果は、メインスタックに追加されます。
Zgarb

回答:


12

パイソン2、752 667 534 506 445 436の 427 404 398 393バイト

これは決して短いことではありません...しかし、私は最善を尽くしました。ゴルフの提案は大歓迎です...

EDIT6:これは関数ではなくスクリプトになりました。それをファイル(shift.py、forex)に保存し、で実行し$ python shift.py '<my_input>'ます。入力は必ず一重引用符で囲んでください。そうしないと、bashが入力文字に狂ってしまいます。

EDIT7:Aaaaaaand ...それはもう読めません。しかし、さらに23バイトを取り除いたので、それで十分でしょう。私も非ゴルフバージョンも投稿します。

EDIT8:@Zgarbのおかげで、ゴルフをもう1つ。

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:...そして@ Sp3000と@orlpによるチャットでの28以上の感謝!

EDIT4:@orlpと@ Sp3000の助けを借りて、すべてのデコレーターを削除し、61バイト短くなりました。

EDIT5:meeeeeeを助けてください、これでゴルフを止めることはできません。最後の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の関数に作用する2つの関数は、任意の数の引数を受け入れる必要があるため*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)3項演算子は通常、何らかの方法で短縮できます。2)['1','0'][...]だけで置き換えることができ'10'[...]ます。3)なぜそうx is 0でないのかx==0(またはx<1)?4)指定し気にしないでくださいRuntimeErrorだけで、except行います。5)Python 2を使用しているため、タブとスペースは異なるインデントレベルとして数えられます-醜いですが、最大25バイト節約できます。
DLosc 2015

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)。(あなたが私を追い越したので、もう少しゴルフゴルフ鉱山...; ^))
DLosc

1
私と同様の問題に遭遇した後、私x.a==1は今何が失敗しているのかを理解していx(y)ます- 真実であるが偽物を返す場合、それはu(...)同様に評価しようとします。しかし、まるで3バイトを保存する必要がないように見えます。私は認めます、サー:あなたは私を超えました。
DLosc 2015

1
たった1つの問題:指定された出力形式にはスペースがありません- さまざまな方法で解決できますが、どちらが最も短いかはわかりません。もちろん、あなたのプログラムはRuntimeErrorwhileを処理しますが、これはユーザーにstderrをリダイレクトするように要求するだけです...したがって、私たちはおそらく問題にさえ直面しています。; ^)
DLosc 2015

1
*_ラムダのforは何ですか?
mbomb007 2015

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を追加します。fxffun

次に、スタック全体を配列としてラップします。サンドボックス化されたコードを実行.runandhideするためのGhostscript拡張であり、呼び出されたプロシージャから前の配列の内容を隠します。このdictコマンドは、新しい辞書をdictスタックにプッシュし、endポップされるまで定義された名前のスコープを絞り込みます。また、=only(私がで使用する出力演算子@)をダミーの演算子に置き換え、試運転中の出力を抑制します。他の言語で見つかっstoppedtryステートメントに相当する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)"0関数内のすべてのが置き換えられると、すべてがに渡されeval、適切なPython関数が呼び出されます。そのほとんどは、6 exec行目の7 行目の大きな文字列から生成されたラムダ関数です。

ネストされた関数の複数のレベルを適切にインクリメント、引用、エスケープすることは、最大の問題でした。関数のネストが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)]

この実装の潜在的な欠陥の1つは、再帰を使用することです。そのため、無限であるべきプログラムは、最大の再帰の深さにかなり早く到達します。(おそらく、無限プログラムを実行するときにstderrをリダイレクトする必要があります。そうしないと、スタックトレースが実際の出力を圧倒します。)それ以外は、すべてが機能しているようです。


上記のプログラムを生成して実行するプログラムを書いていただけませんか。lambda aとのように圧縮可能である必要がある繰り返しコードがたくさんありk.pop()ます。
mbomb007 2015

@ mbomb007 ...私の脳は爆発すると思います。(しかし、最近の編集を参照してください- k.pop()とにかく状況を少し繰り返しを少なくしました。)
DLosc

これらのすべてのラムダに対してexec / translateトリックを実行できますか?それらすべてを1つの文字列に貼り付けますか?
sirpercival 2015

他のコメント:この言語では、関数のネスト<= 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、それは今のパラメータと比較するので、これは、破ったab代わりにa空白とのデバッグに私にいくつかの時間がかかりました。)

z関数は、私のIDEは、これらの機能を実行するため、共有されている-コマンドラインツールは、非共有のものを実行することができます。


ループバージョンは実際にはある時点でStackOverflowErrorをスローし、それで終了します。JVMには再帰スタックの最適化がありません(または、少なくとも私のプログラムでは機能しません)。
–PaŭloEbermann、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.