続:Common Lispって何?

前回のエントリーでは、Common Lispのプロジェクトを開発するための前半として、cl-projectを使ってmyapp2という名前のプロジェクトの骨組みを作り、それを呼ぶためのメインファイルmain.rosを作成する方法を説明した。
今回はその続き。このプロジェクトではフィボナッチ数列を作る。

サブルーチン部分(コア)を作成する

src/myapp2.lispファイルにフィボナッチ数列を作る関数を書く。ポイントは、defpackageマクロ(たぶんマクロ)で外部アクセスを許可したい関数を指定するところ。

src/myapp2.lisp変更前:

$ cd ~/work/sbcl/myapp2
$ cat src/myapp2.lisp
(defpackage myapp2
  (:use :cl))  ; (1)ここを変更
(in-package :myapp2)

;; blah blah blah.  ; (2)これを削除
;; (3)フィボナッチ関数をここに記述

src/myapp2.lisp変更後:

$ vi src/myapp2.lisp
$ cat src/myapp2.lisp
(defpackage myapp2
  (:use :cl)
  (:export :fib))  ; 外部へアクセス可能な関数
(in-package :myapp2)

(defun fib (n)
  (cond
    ((= n 0) 0)
    ((= n 1) 1)
    (t (+ (fib (- n 1)) (fib (- n 2))))))

外部から呼ばれる関数を集めたファイルは「モジュール」と呼ばれ、このようにdefpackageで定義し、in-pacakgeでこれ以降がパッケージの中身だと宣言しているようだ。
パッケージの定義で:exportで指定されたシンボルだけが外部から参照可能なので、:exportは必須と言える。

main関数から別ファイルのfib関数を呼ぶ

fib関数を書いたsrc/myapp2.lispを読むため、main.rosファイルを書き換える。「;;#+quicklisp」のセミコロン2個消し忘れに注意。

main.ros変更前:

$ cat main.ros
#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
  (ros:ensure-asdf)
  ;;#+quicklisp (ql:quickload '() :silent t)
      ;; (1)アンコメント、(2)カッコ内に:myapp2を追加
  )

(defpackage :ros.script.main.3735336286  ; たぶん数字には意味はない
  (:use :cl))
(in-package :ros.script.main.3735336286)

(defun main (&rest argv)
  (declare (ignorable argv)))  ; (3)main関数からmyapp2:fibを呼ぶ
;;; vim: set ft=lisp lisp:

main.ros変更後:

$ vi main.ros
$ cat main.ros
#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
  (ros:ensure-asdf)
  #+quicklisp (ql:quickload '(:myapp2) :silent t)
  )

(defpackage :ros.script.main.3735336286
  (:use :cl))
(in-package :ros.script.main.3735336286)

(defun main (&rest argv)
  (declare (ignorable argv))
  (format t "fib(8)=~d~%" (myapp2:fib 8)))
;;; vim: set ft=lisp lisp:

fib関数を呼ぶときには、「myapp2:fib」というふうに呼ぶ。myapp2:をつけないとエラーとなる。

プロジェクトを実行する

スクリプトとして普通にmain.rosを実行させる。下のようになったら正常に実行できたことになる。

$ ./main.ros
fib(8)=21
$ 

まとめ

以上、なるべくREPL(Read-Eval-Print Loop)を使わずに複数のファイルでCommon Lispを開発する方法を紹介した。たかがフィボナッチ数列を計算するためにここまでたくさんのファイルを作成しなくても、と思われたかもしれないが、ひとつの例なのでご容赦願いたい。 今回のような複数のlispファイル(main.rosもひとつのlispファイル)を使った開発でのポイントは、

  • 呼ぶ側のファイルであらかじめ#+quicklisp (ql:quickload '(:pack1 :pack2) :silent t)のように指定すること。
  • 呼ぶ側は、外の関数を(pack1:fib x)のようにパッケージ名+コロン+関数名と指定すること。
  • 呼ばれる側のファイルであらかじめdefpackage内に(:export :sym1 :sym2)のように宣言すること。

なお、ここまで作成したmyapp2プロジェクトの一連のファイルは以下のレポジトリにアップロードしたので、自分の環境で動作を確認できる。

An example of Common Lisp Project
https://github.com/kitemw/myapp2