2013年5月8日水曜日

[Ruby][Scheme] Micro Schemeの実装(3) let式の評価

let式を扱えるようにした。
https://github.com/takeisa/uschemer/tree/v0.03

lambda式への変換

let式の束縛リストに定義した環境で本体を評価すれば良いので、以下のようにlambda式に変換する。

(let 束縛リスト 本体)

((lambda 仮引数リスト 本体) 引数)

let式を使って作成したクロージャをlambda式に変換する例

(let ((a 1) (b 2))
     (lambda (x) (+ a b x)))

((lambda (a b)
     (lambda (x) (+ a b x))
) 1 2)

処理手順

  1. let式か判定する。
  2. let式の場合は、上記のルールでlambda式に変換する。
  3. 変換後の式を再評価する。

実行例

eval_print([:let, [[:a, 1], [:b, 2]], [:+, :a, :b]], env)
[:let, [[:a, 1], [:b, 2]], [:+, :a, :b]] #=> 3

eval_print([:let, [[:a, 1]], [:lambda, [:x], [:+, :a, :x]]], env)
[:let, [[:a, 1]], [:lambda, [:x], [:+, :a, :x]]] #=> [:closure,
 [:x],
 [:+, :a, :x],
 [{:a=>1},
  {:+=>[:built_in, #<Proc:0x9d4bbe8@uschemer.rb:6 (lambda)>],
   :-=>[:built_in, #<Proc:0x9d4bbc0@uschemer.rb:7 (lambda)>],
   :*=>[:built_in, #<Proc:0x9d4bb98@uschemer.rb:8 (lambda)>],
   :/=>[:built_in, #<Proc:0x9d4bb70@uschemer.rb:9 (lambda)>]}]]

eval_print([[:let, [[:a, 1]], [:lambda, [:x], [:+, :a, :x]]], 2], env)
[[:let, [[:a, 1]], [:lambda, [:x], [:+, :a, :x]]], 2] #=> 3

2013年5月6日月曜日

[Ruby][Scheme] Micro Schemeの実装(2) lambda式とクロージャの評価

lambda式とクロージャを評価できるようにした。
https://github.com/takeisa/uschemer/tree/v0.02

束縛と環境

lambda式評価時の仮引数と引数の対応を束縛とし、その束縛のリストを環境とする。
束縛はハッシュで管理する。
環境の先頭から束縛のハッシュを順に検索して、束縛した値を取得する。

lambda式の評価方法

評価時の環境をひもづけてクロージャを作成する。

クロージャの評価方法

クロージャの仮引数と引数より束縛を作成し、環境の先頭に追加する。
この環境を利用してクロージャの本体を評価する。

実行例

eval_print([:+, :x, :y], [{:x => 123, :y => 456}] + env)
[:+, :x, :y] #=> 579

eval_print([:lambda, [:x, :y], [:+, :x, :y]], env)
[:lambda, [:x, :y], [:+, :x, :y]] #=> [:closure,
 [:x, :y],
 [:+, :x, :y],
 [{:+=>[:built_in, #<Proc:0xb747bca8@uschemer.rb:6>],
   :/=>[:built_in, #<Proc:0xb747b780@uschemer.rb:9>],
   :-=>[:built_in, #<Proc:0xb747baf0@uschemer.rb:7>],
   :*=>[:built_in, #<Proc:0xb747b938@uschemer.rb:8>]}]]

eval_print([[:lambda, [:x, :y], [:+, :x, :y]], 1, 2], env)
[[:lambda, [:x, :y], [:+, :x, :y]], 1, 2] #=> 3

eval_print(
  [[[:lambda, [:y], [:lambda, [:x], [:+, :x, :y]]], 5], 10],
  env
)
[[[:lambda, [:y], [:lambda, [:x], [:+, :x, :y]]], 5], 10] #=> 15

2013年5月3日金曜日

[Ruby][Scheme] Micro Schemeの実装(1) 四則演算の評価


積読していていた
つくって学ぶプログラミング言語 RubyによるScheme処理系の実装 (達人出版会)
を読みつつ、Micro Schemeを作ることにした。

この本にも推奨されていたが、
内容を理解したら、本のコードを見ないで自分で実装する方が理解が深まるので
それに従って、実装した。

作成するプログラム

小さなScheme処理系
Rubyで実装
SchemeプログラムはRubyのリストで表現
例: [:+, [:+, 1, 2], 3]

四則演算できるところまで実装した。
https://github.com/takeisa/uschemer/tree/v0.01

プログラム評価のアルゴリズム

(1)リストの場合
  関数呼び出しとして処理する。
(1)-1 リストの最初のシンボルに対応する関数を取得する。
    ※シンボルに対応する関数は USchemeR.FUNCS で定義している。
(1)-2 二番目以降の要素を引数として、全て評価する(再帰呼び出し)。
(1)-3 評価後の引数を関数に適用する。

(2)リスト以外の場合
a)数値の場合は数値を返す。

b)それ以外は関数を返す。

これだけ。
まだ、lambda式やクロージャも扱えないので、簡単だ。

実行例

eval_print(1)
1 #=> 1

eval_print(:+)
:+ #=> #<Proc:0xb743dc8c@uschemer.rb:7>

eval_print([:+, 1, 2])
[:+, 1, 2] #=> 3

eval_print([:+, [:+, 1, 2], [:+, 3, 4]])
[:+, [:+, 1, 2], [:+, 3, 4]] #=> 10

2013年5月1日水曜日

[Ruby][Emacs]smart-compile.elを使う

EmacsでRubyコードを書くときに、何か便利なものはないかなーと
Web検索していたら、smart-compile.elという、M-x compileの代替となる
便利なELispがあった。

何をするもの?

Smart Compile は M-x comileコマンドの代替で、
ファイル名やメジャーモードに応じて、実行するコマンドを切り替えることが可能になる。
実行するコマンドはカスタマイズできる。

インストール

(1) smart-compile.elをダウンロードする。
(2) ~/.emacs.d/els に格納する。
  ※私は単一のelファイルは els ディレクトリに格納している。
(3) ~/.emacs.d/init.el に以下の設定を追加する。

(add-to-list 'load-path "~/.emacs.d/els/")
(require 'smart-compile)
(setq smart-compile-alist
      (append
       '(("\\.rb$" . "ruby %f"))
       smart-compile-alist))
(add-hook 'ruby-mode-hook
      (lambda ()
        (define-key ruby-mode-map (kbd "C-c c") 'smart-compile)
        (define-key ruby-mode-map (kbd "C-c C-c") (kbd "C-c c C-m"))))

デフォルトだと 拡張子rbは ruby -cw %f を実行する設定になっていたので、スクリプトを実行するように変更した。
Emacs における快適な Ruby 開発環境を求めてを参考に、キーカスタマイズした。
  C-c c : スクリプトを実行する。
  C-c C-c : スクリプトに引数を渡す。

実行例


参考


2013年4月22日月曜日

[SICP][Haskell] ニュートン法で平方根を求める

ニュートン法を使って平方根を求める処理をHaskellで書いてみた。

コード

prec = 0.0001

mysqrt :: Double -> Double
mysqrt x = sqrt_iter 1 x x
  where sqrt_iter guess last_guess x = if good_enough guess last_guess then
                                         guess
                                       else
                                         sqrt_iter (improve guess x) guess x
          where
            good_enough guess last_guess = (abs (guess - last_guess) / guess) < prec
            improve guess x = average guess (x / guess)
                  where average a b = (a + b) / 2

実行結果

*Main> mysqrt 2
1.4142135623746899
*Main> mysqrt 20000
141.42135623738412
*Main> 

SICPのSchemeのコードほぼそのまま。何のひねりもない。
Schemeよりは分かりやすいような気もするけど、この程度じゃあまり変らないよね...
Schemeで hoge? 、Common Lispで hoge-p と命名するような predicateな関数は、Haskellではどのように命名するのが一般的なんだろうか?


コードよりは、
  • ニュートン法とは?
  • どうしてニュートン法で平方根を求めることができるの?
の方が気になって、理解できるまで、いろいろ調べてしまった。
接線の方程式とか、微分とか、いろいろ忘れているなぁ。
たまには数学を再勉強しなきゃ。

参考

2013年4月21日日曜日

[SICP][Lisp]Gauche + Emacsで環境構築

今更だけど、SICP (Structure and Interpreter Computer Programming)を読み始めた。
この本では、Schemeが使われている。
自分でコードを書きながら、読み進めた方が楽しいので、
まずは、Scheme処理系の一つであるGauche+Emacsを使い、
REPLとコード補完できる環境を構築した。

OSはUbuntu、Emacsは自分でインストールしたEmacs 24.2を使用している。

Gaucheのインストール

gaucheはパッケージが用意されているのでapt-getでインストールする。
$ apt-get install gauche

quackとscheme-completeのインストール

パッケージのダウンロード

インストール先は ~/.emacs.d/gauche-el とした。
$ cd ~/.emacs.d/
$ mkdir gauche-el
$ cd gauche-el
$ curl -O http://www.neilvandyke.org/quack/quack.el
$ curl -O http://synthcode.com/emacs/scheme-complete-0.8.11.el.gz
$ gunzip scheme-complete-0.8.11.el.gz
$ mv scheme-complete-0.8.11.el scheme-complete.el

init.elの設定

emacsの設定ファイルinit.elにquackとscheme-completeの設定を追加する。
(add-to-path 'load-path "~/.emacs.d/gauche-el")
(setq quack-default-program "gosh")
(require 'quack)
(require 'scheme-complete)
;;(autoload 'scheme-smart-complete "scheme-complete" nil t)
;; auto-completeを使っているので不要
;; (eval-after-load 'scheme
;;   '(define-key scheme-mode-map "\e\t" 'scheme-smart-complete))
(add-hook 'scheme-mode-hook
  (lambda ()
    (make-local-variable 'eldoc-documentation-function)
    (setq eldoc-documentation-function 'scheme-get-current-symbol-info)
    (eldoc-mode)))
(setq lisp-indent-function 'scheme-smart-indent-function)

動作確認

Emacsを起動後、hello.scm を新規作成する。
(print "こんにちは、世界!")

hello.scmのバッファでC-c C-lを押下する。
mini-bufferで、以下を聞かれる。
それぞれデフォルトのままで良い。
Load Scheme file: (default hello.scm) ~/workspace/sicp/
Run Scheme (default "gosh"):

REPLが開き、hello.scmの実行を結果が表示される。


gosh> こんにちは、世界!
#t
gosh>


とりあえず、他に、以下のコマンドが分かればOK。
  • C-c C-e S式を評価する。
  • M-x run-scheme REPLを起動する。

eldocで関数の説明が表示されるし、コード補完もできる。
いつも使っているslimeとあまり違和感はないようで、なかなか便利だ。


参考

quack
http://www.neilvandyke.org/quack/

scheme-complete
http://synthcode.com/wiki/scheme-complete

auto-complete
http://cx4a.org/software/auto-complete/index.ja.html

SCIPのmobiファイル
https://github.com/twcamper/sicp-kindle

2013年4月7日日曜日

[Raspberry Pi]HUNCHENTOOT + Open JTalkでブラウザ経由で音声出力

Raspberry PiでHUNCHENTOOTが動くようになったので、ブラウザから入力したメッセージを音声出力するWebアプリを作ってみた。

たわいないアプリだけど、小さなRaspberry Piを使って、遠隔操作で音声出力できるのはなかなか楽しい。
デスクトップからRaspberry PiにSSH接続し、tmux + CCL + Emacs(Slime)で開発したが、この程度なら、特にレスポンスが遅いことはなく、ストレスはなかった。
やるなぁ。Raspberry Pi。

ソースはこちらから(https://github.com/takeisa/cl-webapp-talk.git)

動作に必要なLinuxパッケージ

  • open-jtalk
  • open-jtalk-mecab-naist-jdic
  • hts-voice-nitech-jp-atr503-m001

実行方法

  1. Clozure CL(CCL)で、(load "talk.lisp")する。
  2. ブラウザで http://〜:4242/talk を開く。
  3. メッセージを入力してsubmitする。
  4. Raspberry Piがしゃべる!

使用したCommon Lispライブラリ

全てQuicklispでロードできる。
HUNCHENTOOT
Webサーバ。
cl-who
HTMLの生成に使用。
trivial-shell
シェルコマンドの実行に使用。
OSに依存せずにシェルコマンドを実行できる。

ファイル

talk.lisp
HUNCHENTOOTを使ったWebアプリのコード
talk.sh
音声出力したいメッセージを書いたテキストファイルを読み込み、Open JTalkで音声合成後、音声出力するシェルコマンド

動作説明

やっていることは単純。
  1. ブラウザからメッセージを受けとる。
  2. メッセージが空だったら、音声出力しないで、入力画面だけ表示する。
  3. メッセージがある場合は、音声出力する。
  4. 音声出力したいメッセージを message.txt へ出力する。
  5. talk.shを実行する。
  6. talk.shはmessage.txtよりメッセージを読み込み、Open JTalkで音声合成する。
  7. 音声ファイル /tmp/message.wav を出力する。
  8. それを aplay で再生する。再生後は音声ファイルを消去する。

参考ページ