Common Lisp始めました。
いまLand of Lispを読んでいます。
Common Lispでゲームを作って遊べる!楽しい!
読み終わったらまた感想書きます。
Rに慣れるために、Lispインタプリタを書いてみました。
元ねたはPeter Norvigの (How to Write a (Lisp) Interpreter (in Python)) (日本語訳: ((Pythonで) 書く (Lisp) インタプリタ))です。
コードを取ってきます。
$ git clone https://gist.github.com/5598108.git
Rインタプリタを起動してコードを読み込ませます。
repl() を実行するとLispの対話式インタプリタが起動します。
$ cd 5598108/
$ R -q
> source("lisp.R")
> repl()
lisp.R> (+ 1 2)
3
lisp.R> (define l (list 1 2 3))
(1 2 3)
lisp.R> (car l)
1
lisp.R> (cdr l)
(2 3)
lisp.R> (define add2 (lambda (x) (+ x 2)))
#<closure>
lisp.R> (add2 40)
42
lisp.R> (equal? (list 1 2) (list 1 (- 5 3)))
TRUE
lisp.R> (define map (lambda (f l) (if (null? l) (quote ()) (cons (f (car l)) (map f (cdr l))))))
#<closure>
lisp.R> (map add2 (list 10 20 30))
(12 22 32)
lisp.R>
Rの言語機能(ファーストクラスの関数、レキシカルスコープ、クロージャなど)のおかげで、割と楽に書けたと思います。
ただリストの操作はちょっと面倒くさいかな、と思いました。
あと値渡しとか遅延評価とかでところどころはまりました。まだまだ慣れが必要です。
でも今回初めて使った機能がいくつかあって勉強になったし、何より言語処理系を作るのはとても楽しいです。
続編を書きました。((Rで) 書く ((もっとRっぽい) Lisp) インタプリタ)
前回の続きです。
Rのプロンプトで demo(scoping) を実行すると、クロージャの使用例を見ることができます。
SICPの銀行口座 (3.1.1 Local State Variables)
をRで実装したものだと思います。
> demo(scoping)
demo(scoping)
---- ~~~~~~~
Type <Return> to start :
> ## Here is a little example which shows a fundamental difference between
> ## R and S. It is a little example from Abelson and Sussman which models
> ## the way in which bank accounts work. It shows how R functions can
> ## encapsulate state information.
> ##
> ## When invoked, "open.account" defines and returns three functions
> ## in a list. Because the variable "total" exists in the environment
> ## where these functions are defined they have access to its value.
> ## This is even true when "open.account" has returned. The only way
> ## to access the value of "total" is through the accessor functions
> ## withdraw, deposit and balance. Separate accounts maintain their
> ## own balances.
> ##
> ## This is a very nifty way of creating "closures" and a little thought
> ## will show you that there are many ways of using this in statistics.
>
> open.account <- function(total) {
+
+ list(
+ deposit = function(amount) {
+ if(amount <= 0)
+ stop("Deposits must be positive!\n")
+ total <<- total + amount
+ cat(amount,"deposited. Your balance is", total, "\n\n")
+ },
+ withdraw = function(amount) {
+ if(amount > total)
+ stop("You don't have that much money!\n")
+ total <<- total - amount
+ cat(amount,"withdrawn. Your balance is", total, "\n\n")
+ },
+ balance = function() {
+ cat("Your balance is", total, "\n\n")
+ }
+ )
+ }
> ross <- open.account(100)
> robert <- open.account(200)
> ross$withdraw(30)
30 withdrawn. Your balance is 70
> ross$balance()
Your balance is 70
> robert$balance()
Your balance is 200
> ross$deposit(50)
50 deposited. Your balance is 120
> ross$balance()
Your balance is 120
> try(ross$withdraw(500)) # no way..
Error in ross$withdraw(500) : You don't have that much money!
>
銀行口座の状態(total: 残高)はカプセル化されていて、外部からは直接参照できません。
代わりにアクセサ(deposit: 預入れ、 withdraw: 払出し、 balance: 残高確認)
を通してやり取りしています。
オブジェクト指向っぽいものを簡単に実現できていて素敵ですね。
Rでクロージャを使ってみます。
試しにPaul Grahamのエッセイ、Revenge of the Nerds に出てくる「アキュムレータを生成する関数」を書いてみます。
Schemeならこんな感じ。
gosh> (define (make-accumulator n)
(lambda (i)
(set! n (+ n i))
n))
make-accumulator
gosh> (define A (make-accumulator 5))
A
gosh> (A 10)
15
gosh> (A 10)
25
Perl5だと
$ perl -de0
Loading DB routines from perl5db.pl version 1.33
Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(-e:1): 0
DB<1> sub make_accumulator { \
cont: my $n = shift; \
cont: sub { $n += shift } \
cont: }
DB<2> $a = make_accumulator 5
DB<3> p $a->(10)
15
DB<4> p $a->(10)
25
(他の言語での実装はこちらを参照。)
これをRで書くと
> makeAccumulator <- function(n) {
+ function(i) {
+ n <<- n + i
+ n
+ }
+ }
> a <- makeAccumulator(5)
> a(10)
[1] 15
> a(10)
[1] 25
短く書くとこんな感じでしょうか。
makeAccumulator <- function(n) function(i) (n <<- n + i)
<<-ポイントはスーパーアサインメント演算子 <<- です。
通常の代入演算子 <- は同じ環境の変数に代入します。
<<- は入れ子になった環境を順に外側に検索し、最初に変数の定義が見つかった環境に代入します。
見つからなかった場合はグローバル環境に代入します。
詳細は help(<<-) を実行してみてください。
今回の例では、内側の関数から、外側の関数の環境の n に代入したいので、 <- ではなく <<- を使う必要があります。