ASDFをちゃんと理解したい

最近はインターンで日中はずっと仕事していて特にツイッターにネタを投下することがない(余裕がない)ので、ブログを更新するのもだいぶ久しぶりな気がする。

というのも、勤務時間自体はいたって普通なんだけれども通勤時間が割と長くて(片道 : 電車1時間+徒歩30分)、朝7時半とかに家を出て帰宅するのは夜8時とかだったりするので趣味で進めている個人プロジェクトの進捗がゼロなのである。

インターン自体は楽しいのだけれども、やっぱり通勤時間が長い(おまけに満員電車...)と人は精神的に死ぬので、来年から都内に勤務なので全力で職場から近くに住もうと思う。

理想としては職場(汐留)から30分以内に到着できること。そして家賃がある程度抑えられていること。

...となると御徒町とか浅草橋あたり?

今は大学が京都なので半年後には京都から東京に引っ越さなければいけないので考えなければ....

さて、前置きはこんな感じでそろそろASDFの話を始めようと思う。

Common Lispのパッケージについて

プログラミングをやっていて初心者がよくつまづくポイントとしてライブラリモジュールパッケージフレームワークあたりの概念があると思う。

つまづきポイントとして、これらをimportとしたときのパス関係とか名前空間あたりがちょっと大変らしい。

このライブラリとかモジュールとは要するにコードの塊のことなんだけれども、言語とか用途によってその呼び方は結構変わってきたりする。

前にQiitaで何か記事を書いたときにそれはライブラリじゃなくてモジュールですよとコメントをもらったことがあるのだけれども、正直そこらへんの感覚は自分はよくわからない。

というよりも言語によって呼び方が変わる、というのが率直なところ。

PythonだとNumpyとかMatplotlibのことをモジュールと読んでいてDjangoとかTensorFlowとかをフレームワークと呼んでいるらしい(?)

まあそこらへんの呼び方の名前はあんまり議論したところで何か前進があるわけでもないので議論好きな人に任せておくとして、Common Lispではいわゆるモジュールとかライブラリと呼ばれるコードの塊のことをパッケージと呼ぶ。

なのでCommon Lispでパッケージを作るときは defpackage としたりする。

具体的にどういう風にパッケージを定義するかについては後述。

ASDFとは

ASDFとはCommon Lispの依存関係を管理してくれるもののことで、Another System Definition Facilityのこと。

先ほど述べたパッケージについて、どういう順番にロードしたりコンパイルするべきかをまとめておくを制御するもので、これがちゃんと使えなければCommon Lispではプロジェクトが作れないので悲しいことになる。

今回のエントリーのモチベとしてはこのASDFを自力で記述できるように要点をまとめてサクッと簡単なものを作っていこうという感じ。

とりあえず見てみる

語学でもプログラミングでもなんでも、最初に机の上で勉強してばっかで手を動かさないと全く頭に入ってこないので、とりあえず実際のASDFのファイルを見てみようと思う。

わからんことがあったら後から調べていこうと思う。

ということでまずサンプルとしてCL-OpenGLリポジトリの.asdファイルを見てみる。

ちなみに、CL-OpenGLOpenGLCommon Lispで触れるパッケージのこと。

github.com

中身としてはこうなっている。

(defsystem cl-glut    ;; システム定義
  :description "Common Lisp bindings to Freeglut."    ;; 簡単な説明
  :author "Luis Oliveira  <loliveira@common-lisp.net>"    ;; 作者
  :version "0.1.0"    ;; バージョン
  :licence "BSD"    ;; ライセンス
  :depends-on (alexandria cffi cl-opengl)    ;; 使っているパッケージ
  :components    ;; 以下、システムを構成するソースコード
  ((:module "glut"    ;; glutというディレクトリ
    :components    ;; glut内でのシステムを構成するソースコード
    ((:file "package")    ;; package.lispというファイル
     (:file "library"   :depends-on ("package"))    ;; library.lispがpackage.lispに依存
     (:file "state"     :depends-on ("library"))
     (:file "init"      :depends-on ("library" "state"))
     (:file "main"      :depends-on ("library" "init"))
     (:file "window"    :depends-on ("library"))
     (:file "overlay"   :depends-on ("library"))
     (:file "menu"      :depends-on ("library"))
     (:file "callbacks" :depends-on ("library"))
     (:file "misc"      :depends-on ("library"))
     (:file "fonts"     :depends-on ("library"))
     (:file "geometry"  :depends-on ("library"))
     (:file "interface"
            :depends-on ("init" "main" "window" "library" "callbacks"))))))

ざっと簡単にコメントをつけてみた。

とりあえず、ASDFを使うために.asdにはライセンス情報とか使っているファイルを書き込めばいいらしいことがわかる。

で、ここから見た感じ package.lisp がどうやら重要そうなのがわかるので、 package.lisp を見てみる。

(in-package #:cl-user)    ;; 最初にCommon Lispのもともとのパッケージに入る

(defpackage #:cl-glut    ;; cl-glutというパッケージを定義
  (:nicknames #:glut)    ;; 略称
  (:use #:cl #:cffi)    ;; これから定義するパッケージで使う他のパッケージ
  (:import-from #:alexandria #:deletef #:emptyp)    ;; alexandriaというパッケージからdeletefとemptypをimportする
  ;; interface.lisp stuff
  (:shadow #:special #:close)    ;; specialとcloseはCL-USERのものを使う
  (:export     ;; これから外に出すもの
   ;; events / GFs
   #:idle
   #:keyboard
   #:special
   ;; (中略)
   #:set-key-repeat
   #:force-joystick-func
   #:report-errors
   ))

ポイントとして、(in-package cl-user) で最初にCommon Lispのもともとの関数が用意されているパッケージに入らなければいけないこと。

定義の中でどのような設定を行うかについてはコメントを見ればなんとかなる(はず)

で、package.lispはこんな感じで、他のファイルでは最初に

(in-package cl-glut)

;; brah brah brah

として、以降は定義したパッケージの中に入って関数を作っている。

自作する

中身を見てみると意外と簡単なことがわかったので早速自作してみる。

構成として

.
├── src
│   └── main.lisp
└── test-cl.asd

という感じにしてみる。

そして、main.lispでは

(in-package :cl-user)
(defpackage hoge-package
  (:use :cl)
  (:export :hoge))
(in-package hoge-package)

(defun hoge ()
  (format t "This is ASDF test project !"))


(defpackage foo-package
  (:use :cl)
  (:export :foo))
(in-package foo-package)

(defun foo ()
  (format t "Hi! This is foo package !"))

として、先ほど見た通り、パッケージを定義してそのパッケージに入り、中でテキトーな関数を用意する。

さて、今度はtest-cl.asdファイルの編集。

(defsystem test-cl
    :components ((:module "src"
                          :components ((:file "main")))))

本来ならライセンス情報とかいろいろ書くのだけれども今回は省略。

とりあえず重要な:componentsを書いておけばASDFはロードするべき情報がわかっているので大丈夫。

さて、準備ができたのでtest-cl.asdがあるディレクトリでREPLを開いてロードしてみようと思う。

(asdf:load-system "test-cl")

そうするとロードできたのが確認できたと思う。

で、定義したパッケージ名はhoge-packageだったので

(hoge-package:hoge)

として関数を実行する。

f:id:komi1230:20190916170251p:plain

これで良さげ。

foo-packageについても同様。

いじってみる

.asdのファイルをロードするときtest-clとしたけど、この名前はtest-cl.asdというファイルのことなのか中の定義したシステム名のことなのかを調べてみる。

test-cl.asdというファイルをtest-hoge.asdというファイル名に変更。

そうしてREPLで同様にロードしてみると...

f:id:komi1230:20190916171327p:plain

最初にtest-hogeでロードするとダメだけどそのあとtest-clでロードしてみると怒られながらもなんとかいける。

ちなみに最初にtest-clをやるとダメで、そのあとtest-hogeをロードをしてみてもダメ。

順番を変えればいけるらしい?

まあとにかく結論として.asdのファイル名と中の定義するシステム名は一致させるべきという感じ。

まとめ

ASDFの中身を簡単に見ていって、そのあと具体的に.asdファイルを書いて実行してみた。

多分これでASDFは最低限は使えるようになっていると思う。

ASDFの仕様書はかなり膨大で、ライセンスとか作者情報以外にも他にもいろいろオプションがあったりするわけだけど、そこらへんはまた各自でがんばってもらう感じで....

それではお疲れ様でした。

参考

ASDFの仕様書

common-lisp.net