マッカーシーの1959 LISP
1959年初頭、ジョン・マッカーシーは、たった9つの原始関数を定義する画期的な論文を書きました。これらの関数は、まとめると、今日のすべてのLISPのような言語の基礎となります。論文はここからデジタル化して入手できます。
http://www-formal.stanford.edu/jmc/recursive.pdf
、機能である:あなたの仕事は完全に1960年論文に記載どおりにマッカーシーのLISPのためのパーサとインタプリタを実装することでQUOTE
、ATOM
、EQ
、CAR
、CDR
、CONS
、COND
、LAMBDA
、および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つの引数を取ります(引数のいずれかがアトムでない場合、動作は未定義です)。
T
2つの原子が等しい場合は(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人の勝者を許可するかもしれません。 。
((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NIL
どこに(QUOTE NIL)
終わりが入力されているので、これは返す必要がありますかT
?
-> NIL
CONS
「最初の引数を2番目の引数に追加し、新しく作成されたリストを返します」と言いますが、テストケースでは2番目の引数が最初の引数に追加されます。どちらが正しい?