やろーじだい

ブログです

EmacsでCommon Lisp

追記 2017/7/26 こちらの記事は大変古く全く読む価値が無いため, Emacs の環境構築のためには別な記事を読むべきです。最近書かれたものでパッと探してみつけたのを貼っておきます。

 

eshamster.hatenablog.com

良く目を通していませんが,Roswell などを使って導入するのが楽なためそれを使ったもので導入するべきだと思います。

 

 

 

EmacsCommon Lisp

ご挨拶

こんにちは。やほーです。 私は大学が春休みということで、外国に行きました。

ここです

本題

私は今回Common Lispの勉強をしました。

理由は、

  • 関数型言語に興味があった
  • Land of Lispを買ってみたらCommon Lispだった
  • 調べてみるとやりたいと考えている研究の分野で役に立ちそう
  • 楽しそう

と言った理由です。

Common Lisplispの方言の一つで、Schemeと並んで現在主流の方言らしいです。 他にもClojureやArcといったものもあります。

これをMacEmacsとLand of Lispで勉強したので、環境の作成についてまとめて見ました。 (おまけで感想なども書きました)

これ以降、Common LispはCLと表記します。

準備

必要なもの

Emacsは導入されている前提で進めて行きます。

clisp

clispはCL処理系の一つです。 slimeはどんな処理系でもできますが、Land of Lispの中にはclispの独自の命令を使っているところがあるので、 これを使います。

まずbrewを導入します。参考

確認していませんが、macportsにもあると思います。

brewの導入が終わったら

$ brew install clisp

でインストールします。導入が終わったらclispを立ち上げてみます。

$ clisp
i i i i i i i  ooooo   o       ooooooo  ooooo   ooooo
I I I I I I I 8     8  8          8    8     o  8    8
I  \ `+' /  I 8        8          8    8        8    8
 \  `-+-'  /  8        8          8     ooooo   8oooo
  `-__|__-'   8        8          8          8  8
      |       8     o  8          8    o     8  8
------+------  ooooo   8oooooo ooo8ooo  ooooo   8

こういったロゴと、ズラズラと英語が書かれている最後に

[1]>

これ(REPL)が出たら大丈夫です。

[1]> (quit)

で終了できるので一度終了します。周りの括弧も必須です。

linuxも多分aptにあると思うので同じだと思います。 これをEmacs上から利用します。その為に必要なのがemacsの拡張であるslimeです。

slimeの導入と使い方

slimeはEmacs上で動作するLispIDEです。 導入や使い方はこちら

上のページのcclとなっているところをclispに置き換えるだけで大丈夫です。 popwinやacなども使いたい人はその辺りの設定も参考のページの通りしてください。

quickloadというライブラリの話が出てきますが、現在は必要ないはずなので一旦飛ばします。

(私もまだ利用していませんが、かなり便利なものらしいです。参考サイトの第4回で詳しい例があります。)

流れとしては、

M-x slime 

で、slimeを起動します。ここで一旦slimeバッファを閉じて、必要なときにC-c C-zで呼び出すか、 常に開いたままにするかは好きな方でいいと思います。

LoL では、ゲームのためのコードと、例となるコードがあります。 例となるコードはslimeのREPLで試し、ゲームのためのコードはファイルに書いて、

C-c C-c やC-c C-k などを使ってコンパイルして実行、とするのがやりやすかったです。

動作がおかしくなった時には

M-x slime-restart-inferior-lisp

です。長いので

(defalias 'sl-restart 'slime-restart-inferior-lisp)

としています。

また、Slime上でREPLの待機中に[,]キーを押すことでミニバッファに

Command:

と出るので、quitで終了出来ます。 ディレクトリを変えたい時はcdでの移動もここでできます。

読み進める

翻訳者による正誤表や補足

後はどんどん本を読み進め、slime上で動かしたりしましょう。 時々本での評価と、slime上での評価の表示が違うことがありましたが、 実行などには何も問題はなかったはずです。

(clispのversionの違いか、slimeを利用することによるもの?)

また関数の定義時にslimeでエラーの赤線と共に、

WARNING: DEFUN/DEFMACRO: redefining function ...

をよく目にしますが、意図的に上書きしているので心配はないです。 気になる人は前に書いた関数定義を一旦コメントアウトするといいかもしれません。

感想

多少Emacsの設定でlispに触れていましたが、まともに触れたのは初めてです。 理解はできてもこれを自分で思いつくのかというと、正直難しいコードが多いという感想でした。

後半に行くに連れて理解が難しいコードが増えました(特に遅延、AIの辺り)。

慣れれば大丈夫と思いたいですが、まだまだ勉強が必要なようです。 別な本を読んだ後に、もう一度読んでもっと理解を深めたいと思います。

 

難しいところもありますが、初学者に、Lispはもちろんプログラミング自体が楽しいと思わせてくれるCL、関数型言語入門書でした。 読み終えた今では、CLがあれば自分の頭次第でどんなものでも作ることができそうという可能性を感じました。 特にマクロは強力で、使い方によってはすばらしい道具になるだろうなと。

Lispの機能を取り入れた関数型言語も多いようなので、これから他の関数型言語を始めようとしている人にも意味のある本だと思います。

次に読む本も買ったので、残りの休みで読んでいきたいと思います。

読中のメモ(長い)

※メモなので誰かに説明する向けの文章では有りません。   
※間違えた認識をしている可能性があります。
※なにかあればコメントにお願いします。

let

変数定義が、初めなぜ二重括弧なのか分からなかったが、変数定義のリストであるということがわかったときスッキリした。

SchemeCommon Lispの違い

  • Schemeでは、偽値と空リストを別なものとして扱う。
  • Scheme名前空間が1つ。

    よってshcemeは存在する関数と同じ変数は作れない。 Common Lispのコードでは、listやstringなどの、存在する関数名と同じ変数名を結構使っているらしい。

  • 末尾呼び出し最適化

    Schemeでは厳密に要求しているが、Common Lispはその限りではない。

関数型プログラミング

参照透過性、引数と内部で宣言した変数にしかアクセスしない、副作用を持たない。

Common Lispはマルチパラダイム言語なので、副作用のあるコードも書ける。 実際本の中では副作用が利用されている。

cons

破壊的操作しない!

consはリストを変更しているイメージがなんとなくあった。 よく考えたらするわけがないが、気づいた時は最初驚いた。

リストを変更しているように見える関数も、基本的に元のデータをいじることはしていない。(setfなどそういったものに限る?)

setf

ジェネリックなセッター、値を取り出すコードと、値を入れるコードが同じ形で書ける。 セッターに渡す汎変数は、ある要素から値を取り出す形であれば、いくらでも複雑に書ける。

構造体

自動的にオブジェクト生成やアクセスのための関数を作ってくれる -> マクロ 継承などもできる。

遅延計算

7章のthunkの話で、遅延計算をさせるという意味が分からなかった。

 (defun dot->png (fname thunk)
(with-open-file (*standard-output*
              fname
              :direction :output
              :if-exists :supersede)
(funcall thunk))
(ext:shell (concatenate 'string "dot -Tpng -O " fname)))
   (defun test-dot->png (fname nodes edges)
(with-open-file (*standard-output*
                fname
                :direction :output
                :if-exists :supersede)
  (graph->dot nodes edges))
   (ext:shell (concatenate 'string "dot -Tpng -O " fname)))

前者がthunkを利用した本のコードで、後者が中で直接graph->dotを動かしてみたもの。

問題なくファイルに出力できてしまったので、thunkを利用した意味がよくわからなかった。

後に出てくる無方向グラフの時にdot->pngに関数を渡すので、書き換えなくていいという抽象化における利点はあったが、 遅延の話との関係は分からず。

with-hoge

with-open-fileを代表とするコマンドはマクロでできている。 例えばwith-open-fileは自動でリソースを開放してくれる。

stream

string-streamを本来file-streamなどを扱う予定の関数に渡すことでデバッグに使用できる。

クロージャ(closure) not Clojure

無名関数がletで宣言したレキシカル変数を補足し、let外でも参照を持ち続けること。

疑問:関数を呼ぶ度に結果が変わるのでHaskellとかにはないのでは

参考

Haskellにおけるクロージャ Haskellは純粋関数型言語であるため、JavaScriptなどのクロージャと違い、変数(再束縛)がありません。 そのため、関数から無名関数を返す際に無名関数の外側の値を束縛することがHaskellにおけるクロージャといえます。

さらに広義にとらえれば、関数内で一時的な関数を生成してそれを戻り値とするような「関数を返す関数」をクロージャといえます。 (実際には「無名関数の部分適用」と類似した機能といえます。)

…ピンと来ない

メモ化

参照透過性とクロージャを利用して、引数と結果の関係でデータをキャッシュしておき、どうようの引数で呼び出された場合に計算を省くこと。

末尾呼び出し

その関数が最後に処理することが関数の呼び出しであること。

再帰の終了時という意味ではなく、毎回の関数の中での最後で。 アキュームレータを使用することで、呼び出す側の関数を保存する必要がなくなる。

シャドウイング

同じ名前の変数があるときに、近い方の変数が優先されること。 あえて同じ変数名にすることで、外側の変数に誤ってアクセスしないようにする。 しかし同じ変数名を使うのは混乱の元にもなるという問題もある。

マクロ

プログラム実行時前の、コンパイルされる時点(マクロ展開時)に展開される。 バッククオート構文を用いて作成する。 変数補足(意図しないシャドウイング)を避ける為に、gensym関数を利用する。

アナフォリックマクロ

マクロ内の変数を、敢えてマクロ使用者から見えるようにしておくマクロ。 外側で同じ変数を定義していても、マクロ内での変数が優先されるようにする。

DSL(ドメイン特化言語)

Lispでは自分が作りたいものに合わせてマクロを用いて言語を拡張する。

遅延プログラミング

大きな処理を実行時に全てやらず、必要になった(参照が起きた)時にのみ処理する。

原理はわかってもコードが理解できないところが多々出てきた。 結局7章の話はよくわからないまま。

ヒューリスティクス

完全ではないがそこそこ良い結果を素早く得られるようなプログラミングテクニック

日付: 2014-03-04T15:44+0900

Validate XHTML 1.0