Lisp Reader Macro Advent Calendar 2012 の5日目の記事です。
この記事では、プログラミング言語 Racket を簡単に紹介してみます。
Racket は、Scheme(Lisp)をベースとしたプログラミング言語です。
WindowsでもLinuxでも実行できて、GUIアプリケーションも作れます。
もともとPLT Schemeという名前でしたが、色々あってRacketという名前になったようです。
(参考:http://racket-lang.org/new-name.html)
どうせならもっとググりやすい名前にしてほしかったなーと思わないでもないです。
インストール方法は公式サイトを見ていただくとして、
まず最初に典型的な入門プログラム、Hello Worldを書いてみましょう。
#lang algol60
begin
printsln(`hello world');
end
hello.rktと言う名前でファイルに保存してコマンドラインから実行してみます。
コマンド名は racket です。引数としてファイル名を渡すと、プログラムを実行してくれます。
> racket hello.rkt
hello world
次はもう少し複雑な例として、フィボナッチ数を再帰処理で求めるプログラムを書いてみます。
#lang algol60
begin
integer procedure fib(x);
integer x;
begin
integer result;
result := 0;
if x = 0 then
result := 0
else
begin
if x = 1 then
result := 1
else
result := fib(x - 1) + fib(x - 2)
end;
fib := result
end;
printnln(fib(10));
end
Lispは括弧が多いのが特徴の言語らしいですが、このプログラムの括弧の数はたったの10個。
案外少ないです。
> racket fib.rkt
55
以上、入門終わり。
「こんなのLisp(Scheme)じゃない」「algol60って書いてあるぢゃん」と思うかもしれませんが、
ちゃんと実行もできるRacketのプログラムです。
Racketにはソースコードの先頭にかかれた #lang foobar という1行で、
使用する言語を切り替える機能があります。
たとえば、ごく普通のRacketプログラムを書きたい場合は、次のようになります。
#lang racket
(printf "hello world\n")
先頭行の #lang racket は、初期状態としてracketモジュールをインポートした状態で
プログラムを解釈してくれ、というような意味となります。
Racketではソースコードの読み込みを read や read-syntax といった関数で行っています。
そのため、#lang で指定された言語(モジュール)で定義されているread関数によっては、
最初のhello worldのようなLispとはかけ離れた見た目のプログラムも実行できるのです。
Racketインストール時についてくるalgol60 以外の言語としては、静的型付けな Typed Racket や
racket> (require typed/racket)
racket> (let ((a 0)) a)
- : Integer [generalized from Zero]
0
#lang typed/racket
;; typed-racket-test.rkt
(provide fib sum)
(: fib (Integer -> Integer))
(define (fib n)
(cond
((<= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1)) (fib (- n 2))))))
(define: (sum [lst : (Listof Integer)]) : Integer
(let loop ((rst lst) (acc 0))
(if (null? rst)
acc
(loop (cdr rst) (ann (+ acc (car rst)) Integer)))))
racket> (load "typed-racket-test.rkt")
racket> (require (prefix-in t: 'typed-racket-test))
racket> (t:sum '(1 2 3))
6
racket> (t:fib 10)
55
#lang typed/racket
;; typed-racket-err.rkt
(provide sum average)
(define: (sum [lst : (Listof Integer)]) : Integer
(let loop ((rst lst) (acc 0))
(if (null? rst)
acc
(loop (cdr rst) (ann (+ acc (car rst)) Integer)))))
(define: (average [lst : (Listof Real)]) : Real
;; sum関数はIntegerのリストを想定しているが、ここではRealのリストが渡されている
(/ (sum lst) (length lst)))
;; 型があっていないと、実行時ではなくロード時に怒られます。
racket> (load "typed-racket-err.rkt")
typed-racket-err.rkt:14:10: Type Checker: Expected (Listof Integer), but got (Listof Real)
in: lst
errortrace...:
context...:
/usr/racket/collects/typed-racket/typecheck/tc-toplevel.rkt:295:0: type-check
success
/usr/racket/collects/typed-racket/typed-racket.rkt:38:4
/usr/racket/collects/errortrace/errortrace-lib.rkt:434:2: errortrace-annotate
/usr/racket/collects/errortrace/errortrace-lib.rkt:480:4
/usr/racket/collects/racket/private/misc.rkt:87:7
論理型言語 Datalog や
#lang datalog
親(たかし, かあちゃん).
親(たけし, かあちゃん).
親(かあちゃん, ばあちゃん).
親(かあちゃん, じいちゃん).
先祖(A, B) :- 親(A, B).
先祖(A, B) :- 親(A, Z), 先祖(Z, B).
兄弟(A, B) :- 親(A, Z), 親(B, Z), A != B.
先祖(たけし, X)?
兄弟(たけし, X)?
> racket datalog-test.rkt
先祖(たけし, かあちゃん).
先祖(たけし, じいちゃん).
先祖(たけし, ばあちゃん).
兄弟(たけし, たかし).
プレゼンのスライドを作成する言語 Slideshow といったものがあります。
#lang slideshow
;; 1枚目
(slide
#:title "Title-1"
(t "Hello"))
;; 2枚目
(slide
#:title "Title-2"
(item "1st")
(item "2nd"))
他にも、 ドキュメント記述用の Scribble 、 遅延評価を行う Lazy Racket といった言語もあります。
自分で独自の言語を作りたい場合でも、read関数を自分で用意すればよいだけです。
parser-tools/lex や parser-tools/yacc を使えばコンパイラを作る講義も
Racketだけで対応できそうです。
日本ではユーザーが少ないイメージのあるRacketですが、
読み込み処理以外にもいろいろいじれるので、試しに触って遊んでみると楽しいのではないでしょうか。