言語をホモイコニックにする方法


16

この記事によると、次のLispコード行は「Hello world」を標準出力に出力します。

(format t "hello, world")

ホモイコニック言語であるLispは、次の方法でコードをデータとして扱うことができます。

ここで、次のマクロを作成したと想像してください。

(defmacro backwards (expr) (reverse expr))

backwardsはマクロの名前で、式(リストとして表される)を取り、それを逆にします。もう一度、「Hello、world」で、今回はマクロを使用しています。

(backwards ("hello, world" t format))

Lispコンパイラはそのコード行を見ると、リストの最初のアトム(backwards)を見て、マクロに名前を付けていることに気付きます。未評価のリスト("hello, world" t format)をマクロに渡し、マクロはリストをに再配置します(format t "hello, world")。結果のリストはマクロ式を置き換え、実行時に評価されるものです。Lisp環境は、最初のアトム(format)が関数であることを確認し、評価して残りの引数を渡します。

Lispでは、コードがリスト(s-expressions?)として実装されているため、このタスクを達成するのは簡単です(間違っている場合は修正してください)。

次に、このOCaml(ホモイコニック言語ではない)スニペットを見てください。

let print () =
    let message = "Hello world" in
    print_endline message
;;

OCamlにホモイコニシティを追加するとします。OCamlはLispと比べてはるかに複雑な構文を使用します。どうしますか?同種性を実現するために、言語は特に簡単な構文を持っている必要がありますか?

編集このトピックから、Lispのものとは異なるホモイコニック性を達成する別の方法を見つけました:io言語で実装されたもの。この質問に部分的に答えるかもしれません。

ここでは、簡単なブロックから始めましょう。

Io> plus := block(a, b, a + b)
==> method(a, b, 
        a + b
    )
Io> plus call(2, 3)
==> 5

さて、ブロックは機能します。プラスブロックは2つの数字を追加しました。

では、この小さな仲間について内省してみましょう。

Io> plus argumentNames
==> list("a", "b")
Io> plus code
==> block(a, b, a +(b))
Io> plus message name
==> a
Io> plus message next
==> +(b)
Io> plus message next name
==> +

熱い聖なる冷たい金型。ブロックパラメーターの名前を取得できるだけではありません。また、ブロックの完全なソースコードの文字列を取得できるだけではありません。コードに忍び込み、内部のメッセージをたどることができます。そして何よりも驚くべきことです。それは非常に簡単で自然なことです。イオの探求に忠実。Rubyのミラーはそれを見ることはできません。

しかし、おっ、今、そのダイヤルに触れないでください。

Io> plus message next setName("-")
==> -(b)
Io> plus
==> method(a, b, 
        a - b
    )
Io> plus call(2, 3)
==> -1


1
@Bergi Scalaには、マクロに対する新しいアプローチscala.metaがあります。
マーティンバーガー

私は常に同質性が過大評価されていますが。十分に強力な言語であれば、言語自体の構造を反映したツリー構造をいつでも定義でき、必要に応じてソース言語(および/またはコンパイル済みフォーム)との間で変換するユーティリティ関数を作成できます。はい、LISPの方が少し簡単ですが、(a)大部分のプログラミング作業はメタプログラミングであってはなら、(b)LISPはこれを可能にするために言語の明快さを犠牲にしているので、トレードオフに価値があるとは思いません。
ペリアタブレアッタ16

@PeriataBreattaあなたは正しいですが、MPの主な利点は、MPが実行時のペナルティなしで抽象化可能にすることです。したがって、MPは言語の複雑さを増すことを犠牲にして、抽象化とパフォーマンスの間の緊張を解決します。その価値はありますか?私は、すべての主要なPLがMP拡張を持っているという事実は、多くの働くプログラマーがMPが提供するトレードオフを有用であると考えることを示していると思います。
マーティンバーガー

回答:


10

任意の言語をホモイコニックにすることができます。基本的には、言語を「ミラーリング」することでこれを行います(言語コンストラクターの場合、そのコンストラクターの対応する表現をデータとして追加します。ASTと考えてください)。また、引用や引用解除などの追加の操作をいくつか追加する必要があります。それは多かれ少なかれそれです。

Lispは構文が簡単であるため、早い段階でそれを実現していましたが、W。TahaのMetaMLファミリーの言語は、どの言語でも実行できることを示しました。

プロセス全体は、均質な生成メタプログラミングのモデリングで概説されています。同じ素材のより軽量な紹介はこちらです。


1
私が間違っている場合は修正してください。「ミラーリング」は、質問の2番目の部分(io langの同質性)に関連しています。
-19:

@Ignus私はあなたの発言を完全に理解しているかどうかはわかりません。ホモイコニシティの目的は、コードをデータとして扱うことを可能にすることです。つまり、あらゆる形式のコードにはデータとしての表現が必要です。これを行うにはいくつかの方法があります(たとえば、AST準クォート、タイプを使用して、軽量のモジュラーステージングアプローチで行われたようにコードをデータと区別します)が、すべて何らかの形式の言語構文の二重化/ミラーリングが必要です。
マーティンバーガー

@IgnusがMetaOCamlを見ると有益だと思いますか?「同形」であるということは、その場合クォーターであることを意味するのでしょうか?MetaMLやMetaOCamlなどの多段階言語はさらに進化すると思いますか?
スティーブンショー

1
@StevenShaw MetaOCaml、特にOlegの新しいBER MetaOCamlは非常に興味深いです。ただし、ランタイムメタプログラミングのみを実行し、ASTほど表現力のない準引用符でのみコードを表すという点で、ある程度制限されています。
マーティンバーガー

7

OcamlコンパイラはOcaml自体で記述されているため、OcamlでOcaml ASTを操作する方法は確かにあります。

組み込み型ocaml_syntaxを言語に追加し、型defmacroの入力を受け取る組み込み関数を持つと想像するかもしれません

f : ocaml_syntax -> ocaml_syntax

今のタイプdefmacro何ですか?fアイデンティティ関数であっても、結果のコードの型は渡される構文に依存するため、それは入力に本当に依存します。

言語は動的に型付けされるため、この問題はlispでは発生せず、コンパイル時にマクロ自体に型を割り当てる必要はありません。1つの解決策は

defmacro : (ocaml_syntax -> ocaml_syntax) -> 'a

これにより、任意のコンテキストでマクロを使用できます。ただし、これは安全ではありません。もちろん、のbool代わりにを使用するとstring、実行時にプログラムがクラッシュします。

静的型付け言語の唯一の原則に基づいた解決策を持っているだろうに依存するタイプの結果の型は、ここでdefmacro入力に依存するだろうし。ただし、この時点では事態は非常に複雑になります。デビッド・レイモンド・クリスチャンセンによる素晴らしい論文を指摘することから始めます。

結論として、複雑な構文を持つことは問題ではありません。言語内で構文を表現する多くの方法があり、おそらくquote「単純な」構文をinternalに埋め込む操作のようなメタプログラミングを使用するからocaml_syntaxです。

問題は、特に型エラーを許可しないランタイムマクロメカニズムを使用して、この型を適切に作成することです。

持つコンパイル時の OCamlのような言語でマクロのためのメカニズムはもちろん可能であり、例えば参照MetaOcaml

またおそらく有用:Ocamlのメタプログラミングのジェーンストリート


2
MetaOCamlには、コンパイル時メタプログラミングではなく、実行時メタプログラミングがあります。また、MetaOCamlのタイピングシステムには依存型がありません。(MetaOCamlも型が正しくないことがわかりました!)テンプレートHaskellには興味深い中間アプローチがあります。すべてのステージはタイプセーフですが、新しいステージに入るときは、タイプチェックをやり直す必要があります。私の経験では、これは実際にうまく機能し、最終(実行時)段階で型安全性の利点を失うことはありません。
マーティンバーガー

@cody 拡張ポイントを使用してOCamlでメタプログラミングを行うことは可能ですか?
-incud

@Ignus拡張ポイントについてはあまり知りませんが、ジェーンストリートのブログへのリンクで参照しています。
コーディ

1
私のCコンパイラはCで書かれていますが、CでASTを操作できるという意味ではありません...
BlueRaja-ダニーPflughoeft

2
もちろん、それは彼が文は空虚との質問に無関係の両方であることを意味し、その後何なら...:@immibis
BlueRaja -ダニーPflughoeft

1

例として、F#(OCamlに基づく)を検討します。F#は完全にホモイコニックではありませんが、特定の状況下で関数のコードをASTとして取得することをサポートしています。

F#では、次のprintように出力されるとして表されますExpr

Let (message, Value ("Hello world"), Call (None, print_endline, [message]))

構造をより強調するために、同じものを作成する別の方法を次に示しますExpr

let messageVar = Var("message", typeof<string>)
let expr = Expr.Let(messageVar,
                    Expr.Value("Hello world"),
                    Expr.Call(print_endline_method, [Expr.Var(messageVar)]))

分かりませんでした。F#を使用すると、式のASTを「構築」してから実行できるということですか。もしそうなら、eval(<string>)関数を使用できる言語との違いは何ですか?(多くのリソースによると、eval関数を持つことはホモイコニシティを持つこととは異なります-F#が完全にホモイコニックではないという理由ですか?)
incud

@Ignus ASTを自分で作成することも、コンパイラに実行させることもできます。ホモイコニシティは、「言語のすべてのコードにアクセスし、データとして変換することを許可します」。F#では、一部のコードにデータとしてアクセスできます。(たとえばprint[<ReflectedDefinition>]属性でマークする必要があります。)
svick
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.