マッカーシーのLISP


39

マッカーシーの1959 LISP

1959年初頭、ジョン・マッカーシーは、たった9つの原始関数を定義する画期的な論文を書きました。これらの関数は、まとめると、今日のすべてのLISPのような言語の基礎となります。論文はここからデジタル化して入手できます。

http://www-formal.stanford.edu/jmc/recursive.pdf

、機能である:あなたの仕事は完全に1960年論文に記載どおりにマッカーシーのLISPのためのパーサとインタプリタを実装することでQUOTEATOMEQCARCDRCONSCONDLAMBDA、およびLABELすべての機能にする必要があります。答えの正しさを検討する際、このチャレンジテキストよりも論文が優先されますが、以下の9つの機能を要約してみました。言語はすべて大文字であり、エラーチェックは必要ないことに注意してください。すべての入力が有効であると仮定する必要があります。

タイプ

  • McCarthyのLISPには、アトムと、リストまたはアトムである可能性のあるヘッドとして再帰的に定義されるリンクリスト、およびヘッドがアタッチされるリスト(テール)の2つのタイプしかありません。NILは、アトムとリストの両方であるという特別な特性を持っています。
  • 論文によると、原子名は大文字、数字、スペース文字のみで構成されますが、連続するスペースの文字列は1つのスペースと見なされ、先頭と末尾のスペース文字はすべて削除されます。例同等の原子名(スペース文字とアンダースコアに置き換えます): ___ATOM__1__ = ATOM_1。同等でない原子名の例:A_TOM_1 != ATOM_1
  • リストは括弧で示されNIL、すべてのリストの最後に暗黙が示されます。リストの要素はコンマで区切られており、最新のLispのように空白ではありません。したがって、リスト(ATOM 1, (ATOM 2))はになります{[ATOM 1] -> {[ATOM 2] -> NIL} -> NIL}

QUOTE

  • アトム(単一要素)またはリンクリストのいずれかである引数を1つ取ります。引数を正確に返します。
  • テストケース:
  • (QUOTE, ATOM 1) -> ATOM 1
  • (QUOTE, (ATOM 1, ATOM 2)) -> (ATOM 1, ATOM 2)

ATOM

  • アトム(単一要素)またはリンクリストのいずれかである引数を1つ取ります。T引数がアトムの場合(true)、または引数がアトムでない場合NIL(false)を返します。
  • テストケース:
  • (ATOM, (QUOTE, ATOM 1)) -> T
  • (ATOM, (QUOTE, (ATOM 1, ATOM 2))) -> NIL

EQ

  • アトムでなければならない2つの引数を取ります(引数のいずれかがアトムでない場合、動作は未定義です)。T2つの原子が等しい場合は(true)、等しくNILない場合は(false)を返します。
  • テストケース:
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 1)) -> T
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 2)) -> NIL

CAR

  • リストでなければならない1つの引数を取ります(リストでない場合、動作は未定義です)。そのリストの最初のアトム(ヘッド)を返します。
  • テストケース:
  • (CAR, (QUOTE, (ATOM 1, ATOM 2))) -> ATOM 1

CDR

  • リストでなければならない1つの引数を取ります(リストでない場合、動作は未定義です)。リストの最初の原子、つまり末尾を除くすべての原子を返します。すべてのリストが暗示NILで終わるのでCDR、ただ一つの要素を持っているように見えるリストで実行することが返すことに注意してくださいNIL
  • テストケース:
  • (CDR, (QUOTE, (ATOM 1, ATOM 2))) -> (ATOM 2)
  • (CDR, (QUOTE, (ATOM 1))) -> NIL

CONS

  • 2つの引数を取ります。最初はアトムまたはリストの場合がありますが、2番目はリストまたはでなければなりませんNIL。最初の引数を2番目の引数の先頭に追加し、新しく作成されたリストを返します。
  • テストケース:
  • (CONS, (QUOTE, ATOM 1), (QUOTE, NIL)) -> (ATOM 1)
  • (CONS, (QUOTE, ATOM 1), (CONS, (QUOTE, ATOM 2), (QUOTE, NIL))) -> (ATOM 1, ATOM 2)

COND

  • これは、LISPの「if-else」という種類のステートメントです。可変長の引数を取ります。各引数は正確に2の長さのリストである必要があります。各引数リストの順序で、最初の項を評価し、true(T)の場合、関連する2番目の項を返し、関数を終了します。最初の項が真でない場合は、次の引数に移動してその条件をテストし、最初の真の条件に達するまで続けます。引数条件の少なくとも1つは真であると想定できます。すべて偽である場合、これは未定義の動作です。この関数の動作の良い例については、4ページを参照してください。
  • テストケース:
  • (COND, ((ATOM, (QUOTE, ATOM 1)), (QUOTE, 1)), ((ATOM, (QUOTE, (ATOM 1, ATOM 2))), (QUOTE, 2))) -> 1
  • (COND, ((ATOM, (QUOTE, (ATOM 1, ATOM 2))), (QUOTE, 2)), ((ATOM, (QUOTE, ATOM 1)), (QUOTE, 1))) -> 1

LAMBDA

  • 無名関数を定義します。2つの引数を取ります。1つ目は関数の引数を表すアトムのリストで、2つ目は通常は引数を使用するS式(関数本体)です。
  • テストケース:
  • 匿名の「isNull」関数の定義と使用:
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> T
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, ATOM 1)) -> NIL

LABEL

  • 無名LAMBDA関数に名前を付けます。これにより、の本体でその関数を再帰的に呼び出すこともできますLAMBDA。2つの引数を取ります。1つ目はラベルで、2つ目LAMBDAはラベルをバインドする関数です。指定された名前を返します。すべてのLABEL名前のスコープはグローバルであり、aの再定義LABELは未定義の動作です。
  • おもしろいことに、実際にLABELは再帰関数を作成する必要はありません。「Yコンビネーター」でこのタスクを実行LAMBDAできることがわかっているためです。とにかくプログラムを書くのがずっと簡単になります。
  • テストケース:
  • (LABEL, SUBST, (LAMBDA, (X, Y, Z), (COND, ((ATOM, Z), (COND, ((EQ, Y, Z), X), ((QUOTE, T), Z))), ((QUOTE, T), (CONS, (SUBST, X, Y, (CAR, Z)), (SUBST, X, Y, (CDR, Z))))))) -> SUBST
  • (上記を実行した後) (SUBST, (QUOTE, A), (QUOTE, B), (QUOTE, (A, B, C))) -> (A, A, C)

SUBST上記の関数を視覚化するために、次のPythonのような擬似コードとして表すことができます。

def substitute(x, y, z): # substitute all instances of y (an atom) with x (any sexp) in z
    if isAtom(z):
        if y == z:
            return x
        elif True: 
            return z
    elif True:
        return substitute(x,y,z[0]) + substitute(x,y,z[1:])

最終テストケース:

私がそれを正しく転写したら、あなたの通訳はEVALこのコードで解釈できるはずです:

(LABEL, CAAR, (LAMBDA, (X), (CAR, (CAR, X))))
(LABEL, CDDR, (LAMBDA, (X), (CDR, (CDR, X))))
(LABEL, CADR, (LAMBDA, (X), (CAR, (CDR, X))))
(LABEL, CDAR, (LAMBDA, (X), (CDR, (CAR, X))))
(LABEL, CADAR, (LAMBDA, (X), (CAR, (CDR, (CAR, X)))))
(LABEL, CADDR, (LAMBDA, (X), (CAR, (CDR, (CDR, X)))))
(LABEL, CADDAR, (LAMBDA, (X), (CAR, (CDR, (CDR, (CAR, X))))))

(LABEL, ASSOC, (LAMBDA, (X, Y), (COND, ((EQ, (CAAR, Y), X), (CADAR, Y)), ((QUOTE, T), (ASSOC, X, (CDR, Y))))))

(LABEL, AND, (LAMBDA, (X, Y), (COND, (X, (COND, (Y, (QUOTE, T)), ((QUOTE, T), (QUOTE, NIL)))), ((QUOTE, T), (QUOTE, NIL)))))
(LABEL, NOT, (LAMBDA, (X), (COND, (X, (QUOTE, NIL)), ((QUOTE, T), (QUOTE, T)))))

(LABEL, NULL, (LAMBDA, (X), (AND, (ATOM, X), (EQ, X, (QUOTE, NIL)))))

(LABEL, APPEND, (LAMBDA, (X, Y), (COND, ((NULL, X), Y), ((QUOTE, T), (CONS, (CAR, X), (APPEND, (CDR, X), Y))))))

(LABEL, LIST, (LAMBDA, (X, Y), (CONS, X, (CONS, Y, (QUOTE, NIL))))) 

(LABEL, PAIR, (LAMBDA, (X, Y), (COND, ((AND, (NULL, X), (NULL, Y)), (QUOTE, NIL)), ((AND, (NOT, (ATOM, X)), (NOT, (ATOM, Y))), (CONS, (LIST, (CAR, X), (CAR, Y)), (PAIR, (CDR, X), (CDR, Y)))))))

(LABEL, EVAL, (LAMBDA, (E, A), (COND, ((ATOM, E), (ASSOC, E, A)), ((ATOM, (CAR, E)), (COND, ((EQ, (CAR, E), (QUOTE, QUOTE)), (CADR, E)), ((EQ, (CAR, E), (QUOTE, ATOM)), (ATOM, (EVAL, ((CADR, E), A)))), ((EQ, (CAR, E), (QUOTE, EQ)), (EQ, (EVAL, (CADR, E, A)), (EVAL, (CADDR, E, A)))), ((EQ, (CAR, E), (QUOTE, COND)), (EVCON, (CDR, E), A)), ((EQ, (CAR, E), (QUOTE, CAR)), (CAR, (EVAL, (CADR, E), A))), ((EQ, (CAR, E), (QUOTE, CDR)), (CDR, (EVAL, (CADR, E), A))), ((EQ, (CAR, E), (QUOTE, CONS)), (CONS, (EVAL, (CADR, E), A), (EVAL, (CADDR, E), A))), ((QUOTE, T), (EVAL, (CONS, (ASSOC, (CAR, E), A), (EVLIS, (CDR, E), A)), A)))), ((EQ, (CAAR, E), (QUOTE, LABEL)), (EVAL, (CONS, (CADDAR, E), (CDR, E)), (CONS, (CONS, (CADAR, E), (CONS, (CAR, E), (CONS, A, (QUOTE, NIL))))))), ((EQ, (CAAR, E), (QUOTE, LAMBDA)), (EVAL, (CADDAR, E), (APPEND, (PAIR, (CADAR, E), (EVLIS, (CDR, E), A)), A))))))

(LABEL, EVCON, (LAMBDA, (C, A), (COND, ((EVAL, (CAAR, C), A), (EVAL, (CADAR, C), A)), ((QUOTE, T), (EVCON, (CDR, C), A)))))

(LABEL, EVLIS, (LAMBDA, (M, A), (COND, ((NULL, M), (QUOTE, NIL)), ((QUOTE, T), (CONS, (EVAL, (CAR, M), A), (EVLIS, (CDR, M), A))))))

その巨人を実行した後、この行は返されるはず(A, B, C)です:

(EVAL, (QUOTE, (CONS, X, (QUOTE, (B, C)))), (QUOTE, ((X, A), (Y, B))))

しかし、16ページでジョン・マッカーシー自身を引用すると、彼は彼のコンピューターのキャラクターを使い果たしたようです。

より多くのキャラクターがコンピューター上で利用可能であれば、かなり改善される可能性があります...

したがって、このチャレンジにはタグが付けられ、キャラクターの最短回答が勝者になります。標準の抜け穴が適用されます。がんばろう!

文字列の評価に関する注意:Lispを使用し、ホスト言語に合わせて構文を変更してから文字列を使用することで、この課題に勝つことができると考える人もいることを理解しています(eval)。特に識別子の命名規則では、このアプローチが必ず勝つとは確信していません。たとえそうだとしてもeval、すべての言語で文字列を禁止することは主観的で滑りやすい斜面になると思います。しかし、私はこのチャレンジを「正しい」方法で行ったことで人々を罰したくないので、このチャレンジに2人の勝者を許可するかもしれません。 。


1
「IsNull」関数を定義するLambdaの例がありますが、NilがNilを返すように見えます。
nmjcman101

1
あなたは持って((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NILどこに(QUOTE NIL)終わりが入力されているので、これは返す必要がありますかT
nmjcman101

1
右、しかし、あなたは書いた-> NIL
nmjcman101

1
説明の中で、CONS「最初の引数を2番目の引数に追加し、新しく作成されたリストを返します」と言いますが、テストケースでは2番目の引数が最初の引数に追加されます。どちらが正しい?
ヨルダン

1
私はkjetilvalleのlispチュートリアルに基づいて実装していますが、構文は少し異なります。小文字が使用され、コンマは存在しません。単に小文字の変換を実行し、入力文字列からコンマを削除して、上記のインタープリターのデザインに多少適合させることはできますか?私はLispが初めてですが、この挑戦​​を自分の言語で探求したかったのです。これまでのところ、パーサーを実装しています。(私の言語はLispに似ていますが、Node.jsで実装されています)
Andrakis

回答:


17

Python 3、770バイト

これは、stdin / stdoutのREPLです。すべての行が完全なステートメントまたは空であることを期待します。eval実装を短縮するために使用されますが、そうでなければロジックには必要ありません。

import re,sys;S=re.sub
P=lambda l:eval(S("([A-Z0-9][A-Z0-9 ]*)",r"' '.join('\1'.strip().split())",S("NIL","()",S("\)",",)",l))))
d={"QUOTE":'(v,L[1])[1]',"EQ":'[(),"T"][E(L[1],v)==E(L[2],v)]',
"CDR":'E(L[1],v)[1:]',"CONS":'(E(L[1],v),)+E(L[2],v)',"CAR":'E(L[1],v)[0]',
"LAMBDA":'("#",)+L[1:]',"LABEL":'[v.update({L[1]:E(L[2],v)}),L[1]][1]'}
def E(L,v):
 if L*0=="":return v[L]
 elif L[0]in d:return eval(d[L[0]])
 elif L[0]=="COND":return next(E(l[1],v)for l in L[1:]if E(l[0],v)=="T")
 elif L[0]=="ATOM":o=E(L[1],v);return[(),"T"][o*0in["",o]]
 else:l=E(L[0],v);n=v.copy();n.update({a:E(p,v)for a,p in zip(l[1],L[1:])});return E(l[2],n)
R=lambda o:o==()and"NIL"or 0*o==()and"(%s)"%", ".join(R(e)for e in o)or o
g={}
for l in sys.stdin:
 if l.strip():print(R(E(P(l),g)))

1
@Harry最初の2つのテストケースは、最後の仕上げで紹介した小さなバグを修正した後に機能します。evalは問題なく動作します。しかし、このSUBST例はテストケースとして(私の知る限り)まだ壊れています。の1 CONDつがを見つける前に終わりに達しTます。
orlp 16

1
それを修正してくれてありがとう!これは非常に印象的です!現在、すべてのテストケースで機能しますEVAL(最初の試行でその1つを手に入れたのは嬉しい驚きです)。
ハリー

2
また、R(E(P(l)セットアップが大好きです;
ハリー

2
@ハリー私はあなたがそれは事故ではなかったあなたを子供!R = repr、E = eval、P = parse、l = line
orlp 16

4
お知らせしたいのですが、ここで実装について言及した記事を書きました!
ハリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.