Clojure 入門者による【チャットボットづくり】 Part5
大幅に更新が遅れてしまいました。実は腰痛で入院していまして、無事手術を終えて先日退院してきたところです。
入院中あまりにも暇なのでずっと Programming Clojure (第三版)を読んでいたんですが、 Clojure の奥義をそんな簡単に伝えていいの? と思うくらい濃い内容です。
ネット上の(日本語の)情報が少ない、あるいは古いのが難点だと言われがちな Clojure ですが、そもそも何かを勉強したいと思ったら、どんな言語であれ本を読んだほうがいいと痛感しました。例えば map
や filter
, reduce
は他の言語でも実装されていることが多いですが、Clojure の場合は戻り値を遅延させたり、即時適用してパフォーマンスを上げたりと、使い方が複数あります。その根底にあるのはいわゆる「Clojure らしさ」とでも言うべきもので、全体を通じて設計思想を学べる本は一度目を通しておいたほうが良さそうです。
ちなみに Programming Clojure の第三版は英語ですが、いまは自動翻訳がそれなりに賢いので、技術用語がわかれば苦労せず読めるはずです。第二版は通称「孔雀本」と呼ばれる日本語訳がありますが、Clojure 1.3 ベースであるため、最近追加された機能には言及していないという弱点があります。とはいえそれほど激しく仕様が変わる言語ではないため、第二版の内容がまったく通用しないということはないと思います。
さて、かくいう私はまだ半分も読んでいませんが、ひとつ言えることがあります。 Clojure 初心者はここに書いてある内容とコードを信用してはいけない、と。 本を読んだ結果、とてもお手本になるようなコードではないということが判明してしまったため、「オブジェクト指向から来た人間が関数型をなんとなくで書くとこうなる」というケーススタディとして、ニヤつきながら眺めることをおすすめします。
というわけで、今回は正規表現と形態素解析を使って PatternResponder を実装……する予定でしたが、長くなってしまったので先んじて 形態素解析 を実装します。
- 元の実装(Python): sandmark/unmo
- 新しい実装(Clojure): sandmark/unmo-clojure
Clojure 入門者による【チャットボットづくり】 Part4
前回は respond :random
を実装しました。辞書も Map にしようというところまでは決めましたが、それをどう使っていくかに関しては説明していません。
ということで今回は『ランダム辞書の学習』『辞書ファイルの読み書き』をコーディングしていきます。同時にコード量も増えていくことが予想されるので、『ソースファイルの分割』についても。
- 元の実装(Python): sandmark/unmo
- 新しい実装(Clojure): sandmark/unmo-clojure
目次
続きを読むClojure 入門者による【チャットボットづくり】 Part3
大切なことを言い忘れていました。このチャットボットの元ネタは、Ruby 向けの書籍『恋するプログラム』で紹介されていた unmo
という名前のチャットボットです。今ではあまり聞きませんが、一昔前にチャット専門の AI がちょっとしたブームになった頃、そのトンチンカンな返事を揶揄して『人工知能』ならぬ『人工無能』と呼ばれていました。この Muno
のアナグラムで Unmo
となり、雲母(うんも・きらら)という宝石の名前にかかっています。
2005 年に刊行された古い本ですが、思考エンジンをひとつひとつ追加して完成に近づけていく感覚が楽しく、新しい言語を学ぶときはいつもこれを実装することにしています。著者の方が急逝されたのが残念でなりませんが、現在は電子書籍として復刊されているので、この記事でチャットボットに興味を持った方はぜひ手にとってみてください。
恋するプログラム―Rubyでつくる人工無脳 (Mynavi Advanced Library)
- 作者: 秋山智俊
- 出版社/メーカー: マイナビ出版
- 発売日: 2014/12/04
- メディア: Kindle版
- この商品を含むブログを見る
というわけで今回は『辞書』および『ランダムレスポンス』を実装していきます。
- 元の実装(Python): sandmark/unmo
- 新しい実装(Clojure): sandmark/unmo-clojure
Clojure 入門者による【チャットボットづくり】 Part2
Coders At Work (原著: Peter Seibel, 翻訳: 青木靖)という本があります。第一線級のハッカー達に「エディタは何を使ってます?」「デバッグはどうしてます?」といった基礎的な質問から、「プログラマ全員が読むべき本はありますか?」といった抽象的な質問まで、Peter Seibelががっつり食いついてインタビューしている濃い本なのですが、Erlang の開発者であるジョー・アームストロングがこんなことを言っていました。
ーー コードを書く前に多くの時間を考えて過ごすということですが、そのときには実際どんなことをするんですか?
アームストロング ああ、メモを書きます。ただ考えているわけではありません。紙にいろいろ落書きします。(中略)もう1つ重要なことは、同僚に「君だったらこれをどう解く?」と尋ねることです。同僚のところに行って、「こうやればいいのか、ああやればいいのか迷っている。AかBか選ばなきゃいけない」と言い、そのAとBについて説明している途中で、「ああ、Bだね。ありがとう。助かったよ」というようなことがよくあります。
これを読んで不思議な気持ちになりました。私も質問の前の説明段階で自己解決してしまうことはよくありますし、他の人が勝手に納得して席に戻っていくのも見たことがあります。「あるあるネタ」ではありますが、そういうことはプログラマーとして力をつけていくと、すっかり無くなることだと思っていたのです。世界レベルの言語とフレームワークを作り上げる人にも、私と同じように脳みそが入っているらしいということが、妙にリアルに感じられました。
ーー あるコンピュータサイエンス学科では教官の部屋にぬいぐるみがあって、教官を患わせる前にそのぬいぐるみに向かって自分の問題を説明しなければならないという決まりになっているそうです。「あの、クマさん、私が取り組んでいるのはこういうことで、このようなアプローチをしているんですが、……そうか! 分かりました」
アームストロング 本当ですか? 私もやってみるべきですね。
ーー あなたのあの猫に話されるといいですよ。
アームストロング 猫にね。いやまったく! 私より若干年上で非常に頭のいい人と一緒に仕事をしているのですが、私が彼の部屋に行って質問をすると、どの質問に対しても彼は、「プログラムはブラックボックスだ。入力と出力がある。そして入力と出力の間には関数的な関係がある。君の問題の入力は何? 出力は? その2つの間の関数的な関係は?」と聞くのです。そして会話の途中のいずれかの時点で、私は「ああ、君って天才だよ!」と言って部屋を飛び出すことになるのですが、彼のほうは驚いて頭を振りながら、「いったい問題は何だったんだろう。あいつちゃんと説明したためしがない」とつぶやくのです。だから彼は問題を説明して聞かせるクマと一緒ですね。
前回は記事本文を書きながら「コードをリファクタリングせねばならぬ……!」という謎の使命感に燃えておりました。文章化するからにはせめて自分で納得行くコードを載せたいという虚栄心がそうさせたのですが、動機がどうであれエディタの操作を覚えたり、イディオムを検索したのは事実。
そう考えると、私にとってのクマのぬいぐるみはこのブログであり、読者ということになります。Rich Hickey 効果で図らずも別ベクトルの注目を集めた気がする前回のことは忘れて、今回も「なんでこういう仕様にしたんだっけ」と自問自答しながら、ちまちまコードを載せていきます。
- 元の実装(Python): sandmark/unmo
- 新しい実装(Clojure): sandmark/unmo-clojure
Coders at Work プログラミングの技をめぐる探求
- 作者: Peter Seibel,青木靖
- 出版社/メーカー: オーム社
- 発売日: 2011/05/25
- メディア: 単行本(ソフトカバー)
- 購入: 11人 クリック: 360回
- この商品を含むブログ (36件) を見る
Hy の macroexpand が S 式じゃない→直した
Python
へ直接コンパイルできる Lisp 方言 Hy が楽しいです。 しかし Lisp 族のつもりでいると思わぬ落とし穴があったり( let
, cons
がないなど)するので、 とりあえず macroexpand
の結果を S 式にしてみます。
例えば let
をマクロで定義してみましょう。 let
の正体は lambda
というか関数なので、 無名関数を使って実装します。
(defmacro let [values &rest body] (setv var-names (list (map first values)) var-vals (list (map second values))) `((fn [~@var-names] ~@body) ~@var-vals))
デフォルトの macroexpand-1
の挙動はこんな感じ。
=> (macroexpand-1 '(let ((var 1)) (print var))) HyExpression([ HyExpression([ HySymbol('fn'), HyList([ HySymbol('var')]), HyExpression([ HySymbol('print'), HySymbol('var')])]), HyInteger(1)])
待って読めない。
もうちょっと Lisper 向けの表現(= S-expression)に近づけてほしいので、 hy
コマンドを起動するときに引数をつけてあげます。
hy --repl-output-fn=hy.contrib.hy-repr.hy-repr
これで macroexpand
してみると、今度はこんな感じに。
=> (macroexpand-1 '(let ((var 1)) (print var))) '((fn [var] (print var)) 1)
なぜか一行で表示されましたが、それでもこちらのほうが何倍も読みやすいです。
私は Emacs 使いなので hy-mode
経由で hy
シェルを起動しているんですが、その場合は hy-shell-interpreter-args
(デフォルト "--spy"
)を変更すればよい感じです。
(add-hook 'hy-mode-hook (lambda () (setq hy-shell-interpreter-args (concat "--repl-output-fn=hy.contrib.hy-repr.hy-repr " hy-shell-interpreter-args))))
Hy
がもっと繁栄しますように。