Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

「Lispインタープリター」勉強会 2014.12.04

[説明に使用するソースコードの場所]
http://goo.gl/wtkXro

この資料は以下の勉強会にて使用したスライドです。
「ギークエンジニア必見!Lispインタープリター」勉強会 with すごい広島 (http://great-h.doorkeeper.jp/events/16621)

このLisp勉強会で大いに参考にしたのは、Peter Norvig による、「(How to Write a (Lisp) Interpreter (in Python))」です。非常に短いPythonプログラムでLispインタープリターが実装されています。
http://norvig.com/lispy.html
http://norvig.com/lispy2.html

[勉強会の内容]
「プログラマーなら一度はLispを作る」という言葉は有名です。ですが、実際に作ったことがある人はあまり居ないと思います。
理由として「このような技術は実務には関係ない」とか、「作ろうと思っても、簡単に実現可能なほど、まとまった情報が無い」などが考えられます。
この勉強会では、プログラミング言語としてJavaScriptを用い、実際に動作する、古風で小さなLispインタープリターを、段階を追って作成します。JavaScriptによるLispインタープリターの実装は、最初は22行で実装した四則演算インタープリターの説明から開始します。最終的にはLispのマクロをサポートした、147行のLispインタープリターに育て上げます。
なお、Lisp言語を知らない人も多いと思われますので、そのような方向けに、Lisp言語の非常に基本的な部分から説明したいと思いますので、ご安心ください。
Lisp言語の全機能を網羅することはしませんが、この勉強会に参加すれば、Lispインタープリターがどのように動作しているのか、なんとなく分かった状態になることを目指しています。そして、それが分かれば、プログラミング言語を見る目が少し変わると思います。

  • Be the first to comment

「Lispインタープリター」勉強会 2014.12.04

  1. 1. ウルシステムズ株式会社 http://www.ulsystems.co.jp mailto:info@ulsystems.co.jp Tel: 03-6220-1420 Fax: 03-6220-1402 「Lispインタープリター」勉強会 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 2014/12/03 講師:近棟稔 説明に利用するソースコードの場所は以下の通りです [短縮URL] http://goo.gl/wtkXro [URL] https://22662085c43898e6b7217dd85e8ec1a5e8bb286f.google drive.com/host/0B3XsaTcJZ4A3ZmpfNXZyZmZqR0U/
  2. 2. はじめに ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 1 「プログラマーなら一度はLispを作る」という言葉は有名です。ですが、実際に作ったことがある 人はあまり居ないようです。理由として「このような技術は実務には関係ない」とか、「作ろうと 思っても、簡単に実現可能なほど、まとまった情報が無い」などが考えられます。 この勉強会では、プログラミング言語としてJavaScriptを用い、実際に動作する、古風で小さ なLispインタープリターを、段階を追って作成します。 JavaScriptによるLispインタープリターの実装は、最初は22行で実装した四則演算インタープ リターの説明から開始します。最終的にはLispのマクロをサポートした、147行のLispインター プリターに育て上げます。 なお、Lisp言語を知らない人も多いと思われますので、そのような方向けに、Lisp言語の非常 に基本的な部分から説明したいと思いますので、ご安心ください。 Lisp言語の全機能を網羅することはしませんが、この勉強会に参加すれば、Lispインタープリ ターがどのように動作しているのか、なんとなく分かった状態になることを目指しています。そして、 それが分かれば、プログラミング言語を見る目が少し変わると思います。
  3. 3. 今回説明するLispインタープリターの由来  Peter Norvig による、「(How to Write a (Lisp) Interpreter (in Python))」を参考にしました。Python で作成されており、たった113行でインタープリターが実装されています。 http://norvig.com/lispy.html http://norvig.com/lispy2.html  Peter Norvigは、GoogleにてResearch Directorをやっている人です。また、UdacityというWeb上の オンラインコースでArtificial Intelligenceなどを教えています。TEDにて公演を行ったこともあります。 (ちなみに近棟もUdacityでNorvigの授業を受けました)  この勉強会で説明するのは、勉強会用に特別に作成したLispインタープリターです。 JavaScriptを用いて実装しました。文法は、Clojureから借用しています。言語の名前はmylispと名 付けました。最終的なコード量は147行になりました。 なお、コードエディター部分に関しては、CodeMirror (http://codemirror.net/) を利用しています。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 2 http://www.ted.com/talks/peter_norvig_the_100_000_student_classroom
  4. 4. mylispの全体像(147行) [マクロ展開処理部分(16行)] ソースコードを前処理する、「マクロ」という機能を提供します。 C言語のマクロと意味的には同じですが、Lispのマクロは非常に強力なため、 ソスコードジェネレータであると捉えたほうが正確です。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 3 [パーサー部分(24行)] ソースコード文字列をAST(抽象構文木)の構造に組み立てます。 [インタープリターの変数格納用コンテナー(9行)] 変数や関数を格納するためのコンテナーを用意します。 [ASTを読みながら、プログラムの実行を行う部分(34行)] ASTを読みながら、プログラムを実行します。インタープリターの中核部分です。 [組み込みライブラリー(60行)] 足し算、引き算など、プログラムを作る際に最低限無いと困る処理を 記述した部分です。
  5. 5. 四則演算インタープリター ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 4
  6. 6. 四則演算インタープリターの全体像  以下の22行の実装で四則演算インタープリターが作れます。 手始めに、このインタープリターの詳細な解説を次ページ以降で説明していきます。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 5 プログラムの実行を行う部分 組み込みライブラリー
  7. 7. 四則演算インタープリターの使い方  この四則演算インタープリターには、前もって5つの関数が用意されています。新たな関数を新規に定義すること や、変数を定義することは今のところ出来ません。前もって用意されている5つの関数は以下のものです。  ここで、Javaなどの言語では関数名に使えない「+」などの記号が関数名になっている事に違和感を覚えるかもし れません。ただ、Lispではしばしばこのような記号を関数名として使用します。ちょっと名前の付け方が違う程度で あると思ってください。たとえば「add」のような関数名の代わりに「+」という名前の関数を付けます。  四則演算インタープリターを使った計算は、以下のように行います。 +関数をadd関数として記述した場合、 JavaScript的に書くと、add(1, 2, 3) と同じ ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 6 関数名引数処理内容 + 数値(可変長引数) 引数の数字の和を計算 - 数値(可変長引数) 引数の数字の差を計算 * 数値(可変長引数) 引数の数字の積を計算 / 数値(可変長引数) 引数の数字の商を計算 mod 第一引数は分子、第二引数は分母引数の2つの数字の余りを計算 つまり、ある関数を実行したい場合は、 配列の先頭要素が関数名を入れ、 第二要素以降に引数を入れるとします。 関数 +関数をadd関数とし、/関数をdiv関数と記述した場合、 JavaScript的に書くと、add(1, div(1, 2)) と同じ [関数名, 引数1, 引数2, 引数3] 次ページ以降で、四則演算インタープリ ターの中身の説明をしていきます。
  8. 8. 変数や関数のグローバル変数領域:globalEnv  インタープリターに限らず、ほとんどのプログラミング言語で共通することとして、変数や関数を格 納するための空間が用意されています。この空間に、名前をキーとして、値を取り出せるようにした り、名前をキーとして関数の実装を取り出せるようにしたりします。 このインタープリターでは、そのような空間としてglobalEnvというオブジェクトを用意しています。構 造は単なるHashMapです。名前をキーとして値や関数の実装を取り出せるようになっています。  単純な計算であればglobalEnvの呼び出しでも可能です。  このglobalEnvというHashMapは、以降の説明でも出てきます。globalEnvは、ひとことで言うと「グ ローバル変数領域」なので、このインタープリターの最終形まで存在し続けます。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 7 +関数をadd関数として記述した場合、 JavaScript的に書くと、add(1, 2, 3) と同じ mod(5, 3) と同じ
  9. 9. インタープリターの心臓部:evaluate  ほとんどのインタープリターの心臓部はevalやevaluateという関数で実装されています。JavaScript のevalもその1つです。以下は四則演算インタープリターのevaluate部分です。  上記のコードの処理内容を日本語で記述すると、以下のようになります。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 8
  10. 10. 四則演算インタープリターの計算の進行例: 入力がevaluate(['mod','5','3'], globalEnv); の場合  evaluate(['mod','5','3'], globalEnv); ↓  'mod'と'5'と'3'がevaluate(x, globalEnv)のxとして代入される。それぞれ以下の様になる。 evaluate('mod', globalEnv) → function(num, div) {return Number(num) % Number(div);} evaluate('5', globalEnv) → '5' evaluate('3', globalEnv) → '3' ↓  mod(5, 3)が計算される。より正確には、mod.apply(null, ['5', '3']) が実行される。このapplyとい う関数の意味は、mod('5', '3') と同じ。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 9 ['mod','5','3'] [mod関数の実装,'5','3'] mod関数の実装に引数('5','3')を渡して実行
  11. 11. 四則演算インタープリターの計算の進行例: 入力がevaluate(['+', '1', ['mod','5','3']], globalEnv); の場合 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 10 ['+', '1', ['mod','5','3']] [+関数の実装, '1', ['mod','5','3']] [+関数の実装, '1', [mod関数の実装,'5','3']] [+関数の実装, '1', mod関数の実装('5','3')] [+関数の実装, '1', '2'] +関数の実装('1', '2') 3
  12. 12. そもそもLispとは? ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 11
  13. 13. Lispは1958年に発明された歴史ある言語です  Lispは1958年にJohn McCarthyによって発明されました。時代的にはコンピュータの黎明期にあた ります。非常に歴史のある言語です。関数型言語であり、なおかつ公式に標準化された最初のオ ブジェクト指向言語でもあります。 太平洋戦争(3年10ヶ月)  Lispは、プログラムで扱う主たるデータ構造の1つを リスト構造とし、また、プログラム自身をリスト構造の入れ子 で表現する言語です。プログラムコードはデータなので、 プログラムを機械的に(プログラムによって)変形したり 自動生成したりしやすいという特徴を持ち、ここがLispの 最も大きな強みとなっています。 言ってみれば、Lispは、コードの自動変形・自動生成の 機能を内蔵したプログラミング言語です。 このコードの自動生成機能は、「マクロ」と呼ばれています。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 12 1957 Fortran John McCarthy 1959 COBOL (google画像検索より引用) 1946 ENIAC 1936 Alan Turingの Turing Machine (仮想機械) 1947 ノイマン型 アーキ テクチャ 1941 Zuse Z3 世界初の コンピュータ 1958 LISP 実装 (構想は1956年) 5年5年10年
  14. 14. 四則演算インタープリターに 条件分岐「if」を導入 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 13
  15. 15. evaluate関数が条件分岐「if」を解釈できるようにします。  今までのevaluate関数に加筆して、「if」を解釈するようにしたものを以下に示します。  実行例 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 14 追加部分 (false ? 2 : 3) と同じ
  16. 16. 「if」の利用例:globalEnvにブール値関数(true/falseを返却する関数)と alert関数を追加して試す。  ifの条件に利用可能な関数が無いため、globalEnvに以下の様に追加します。  JavaScriptのalert関数(ダイアログを出す関数)を利用した例は以下のようになります。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 15 追加部分
  17. 17. 四則演算インタープリターに 逐次実行「do」を導入 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 16
  18. 18. evaluate関数が逐次実行「do」を解釈できるようにします。  「do」の導入のためには、do関数をglobalEnvに追加します。  そもそも逐次実行とは?? プログラムはループなどが無ければ、上の行から下の行に向かって順番に実行されます。これが 逐次実行です。「do」の場合は、この逐次実行実行に加えて、最後に計算した計算結果を返すとい う役割もあります。  「do」の簡単な使い方 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 17 追加部分 上から順にalertを表示し、 doの最後に指定した「5」を 返却している。 [注] Common Lisp では、「do」はループを意味しますが、 Clojureを真似て、mylispでは逐次実行の意味としています。
  19. 19. 定数をプログラムから 作成可能にする ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 18
  20. 20. 「def」により、evaluate関数内でglobalEnvに対して新たな変数を 定義可能にします。defは定義することを意味するdefineの略です。  globalEnvに新たなkey/valueを定義可能にするための「def」を処理する機能をevaluate関数に追 加します。そのために、HashMapであるenvに指定されたオブジェクトを代入します。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 19 追加部分
  21. 21. 「def」の利用例  「def」を利用し、PIを定義してみます。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 20 PIを定義していない場合、PIを評価しても、 PIというシンボルをそのまま返却します。 PIを3.14159と定義します。内部的には、 globalEnvのHashMapに代入しています。 PIを再度評価すると、今度は3.14159が返ります。 globalEnvに、「PI」というキーで「3.14159」が入っていることが分かります。
  22. 22. 関数をプログラムから 作成可能にする ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 21
  23. 23. evaluate関数内で無名関数を作成可能にします。  いよいよインタープリター内で関数を作成可能にします。実現方法としては、JavaScriptの関数オブジェクトを作成 することで多くの複雑な部分を単純化しています。無名関数の作成は、ここではJavaScriptの関数の生成に置き 換わっているに過ぎません。最低限やらなければならないことは、実引数を仮引数に代入する作業となります。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 22 追加部分 [悪] ここでグローバル変 数領域であるenv (すなわちglobalE nv)に仮引数をキ ーに実引数を代入 してしまっています。 要改善点です。
  24. 24. 「fn」の利用例  「fn」によって関数オブジェクトを作成し、そのオブジェクトを「def」によってincrementというキーに保 持させてみます。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 23 JavaScriptにおける var increment = function(x){return 1 + x;}; このように、 JavaScriptの 関数オブジェクト へと評価されます。 increment(2)を呼び出してみます。 1 + 2 の計算結果である3 が返ってきます。 しかし、現在の関数の実装では、引数の値を直接globalEnvに代入しているために、 関数実行後もglobalEnvに関数の引数が残ってしまいます。 これを解決するために必要なのは、グローバル変数領域であるglobalEnvのHashMapに 引数を保持させるのではなく、メソッド呼び出しに閉じた、階層化されたHashMapを使う必要があります。 つまり、呼び出し階層が1つ深くなると、その階層専用の領域を持つ必要があります。
  25. 25. 変数が必ずglobalEnvに代入される事の問題点  現状、何かの値や関数に名前を付ける(defする)場合、必ずglobalEnvに代入を行っています。 このような仕様では、具体的には以下のような問題が発生します。 1. 関数内でdefを使うと、「関数ローカル」な変数を定義しようとしても、必ずglobalになります。 (この挙動は、JavaScriptでvarを付けずに変数定義した場合と似ています。) 2. 関数呼び出し時に渡した値と引数の名前とのマッピングもglobalEnvに登録しているため、関数呼 び出しをするたびに引数で渡した値がglobalEnvに格納されてしまいます。 JavaScriptにおける、このような挙動をさせたい。 つまり、「a」も「b」も1や2を格納していない状態になるべ き。しかし今はaに1が入り、bに2が入るような挙動をして しまう。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 24 結局、globalEnvを使った変数のマッピングを していてはダメで、関数ローカルなマッピングが 必要!
  26. 26. 関数内の変数を「ローカル」にする ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 25
  27. 27. シンボル(変数名)でオブジェクト(変数や関数)を引き出す仕組みを改善する。 (現状の単純なHashMapから、ローカルスコープを持ったHashMapへ)  単純なHashMapから、ローカルスコープを持ったHashMapへ変更するため、以下のようなデータコ ンテナを作ります。  このデータコンテナを用いて、以下のような構造を作れるようにします。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 26 globalEnv env2 env3 env1 a=0 a=1 a=3 globalEnvはグローバル変数領域とし、 env1, env2, env3 はそれぞれ関数 ローカルの変数領域とします。 それぞれの関数ローカルの変数領域 (すなわち、ローカル変数領域)は、 自身の変数領域に該当シンボルが無い 場合、よりグローバル側の環境を探しに 行きます。
  28. 28. 関数のローカルスコープを持つようにevaluateを修正  全体的にenv.bindingに対する処理に変更します。また、関数呼び出しの際に関数ローカルのenvを作るように修 正します。変数を探す際にはfindEnvを用いてenvの階層構造をさかのぼって値を探すように修正します。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 27 [改善] 関数呼び出し毎に 新たなHashMapを 作ることで、グローバ ル変数領域を汚さない ようにする。また、「内」 から「外」の変数は見え るようにする。
  29. 29. 可変長引数のサポート(余談) ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 28
  30. 30. 可変長引数のサポート(余談)  サポートしたい可変長引数の文法 func(a & b) という関数を作り、func(1, 2, 3)という呼び出しをすると、 引数a と引数b に、それぞれ以下の情報が入るようにしたい。 a ← 1 b ← [2, 3]  可変長引数をサポートする前の姿  可変長引数をサポートした後の姿 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 29 ← Javaにおけるfunc(int a, int... b) {...} のような意味 mylispでの書き方に従うと・・・ ["def", "func", ["fn", ["a", "&", "b"] ] となる。 「&」が登場した後の引 数に、残りの実引数を リストとして保持する。
  31. 31. 可変長引数の利用例(余談)  以下に可変長引数の利用例を示します。「&」に特別な意味を持たせることで、このように可変長引 数を実現することが可能です。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 30 JavaにおけるalertMsg(String message, int... xs) {...} のような意味 このように、任意の数の引数を与えることが可能です。 引数message ← "テスト" 引数xs ← ["1","2","3"]
  32. 32. プログラムの変換・生成のために マクロを導入 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 31
  33. 33. evaluateの前処理としてプログラムを変形するのがマクロの仕事  今まで見てきたmylispは、ソースコードがJavaScriptの配列表現でした。よって、JavaScriptの配列 操作を行えば、自由に変形することが可能です。この変形操作がマクロの本質です。 マクロ導入前マクロ導入後 ['+', '1', ['mod','5','3']] evaluate ・・・何かに変形・・・  マクロの利用例として、以下のような事が考えられます。  ソースコード上に出現する退屈なコードの記述を、少ない記述で自動生成するため。  DSLを作るため。(DSLを作れば記述を簡素化可能)  パフォーマンスを出すために、既存コードを別の構造に変換するため。  パフォーマンスを出すために、特定の計算をevaluate前に計算済みの状態にしてしまう。  「オブジェクト指向」など、新たな言語上のパラダイムが世の中に登場した際に、言語自身を拡張するため。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 32 ['+', '1', ['mod','5','3']] evaluate 3 3 マクロ展開
  34. 34. マクロを定義し、同時にマクロを展開する(プログラムコードを変換する)ために は、globalEnvとは別にmacroTableを作ります。  マクロ名をキー、プログラムコードを変換する関数を値とするHashMapを作ります。このHashMapをmacroTable と名付けます。  一方で、マクロはJavaScriptの配列をさまざまに操作する必要があるので、配列の操作関数を一式導入します。 ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 33
  35. 35. マクロの利用例  マクロの利用例として、条件分岐の機能を持つ「when」をマクロで実装してみます。  実行例を以下に示します。  実行時には、以下の様にソースコードが変形され、evaluateされます。 ["if", "true", [do, ["+", "2", "3"]], "null"] ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 34 ["when", "true", ["+", "2", "3"]] evaluate 5 マクロ展開 ソースコードの変換方法をmacroTableに定義 上のマクロを使って展開した後に実行
  36. 36. JavaScriptの配列表現をやめ、 文字列表現へ ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 35
  37. 37. JavaScriptの配列表現は見づらいので、標準的なLisp表現を解釈可能にしま す。そのためには、文字列のパーサーを書く必要があります。  文字列のパースを行い、JavaScriptの配列を組み立てる処理は以下のように書けます。 文字列JavaScriptの配列形式(ASTと呼ばれる) ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 36 (+ 1 (mod 5 3)) ['+', '1', ['mod', '5', '3']] parse
  38. 38. まとめ ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 37
  39. 39. インタープリターの全体処理イメージ  インタープリターは、以下の様な処理フローで処理されます。  Lispで特殊なのは、マクロを用いて一旦プログラムが変形されたり、生成されたりするフェーズがあ ることです。このようなフェーズの存在は、一般的にはC言語のプリプロセッサーと同一といえば同 一ですが、C言語のプリプロセッサーなどよりも非常に強力なため、Lispを特別なものにしています。  なぜLispのマクロが強力なのかといえば、プログラムがリストの入れ子構造で表現されているため に、マクロからプログラムコードを扱いやすいという特性によります。 (when true (+ 2 3)) 文字列 ["if", "true", ["+", "2", "3"], "null"] ULS Copyright © 2014 UL Systems, Inc. All rights reserved. 38 ["when", "true", ["+", "2", "3"]] JavaScriptの配列形式(ASTと呼ばれる) parse マクロ展開←この処理はソースコードの自動生成といっしょ evaluate 5

×