Lambdaカクテル

Common Lispを書くMT-03ライダー(初心者)です

Common Lisp開発序ノ口 〜プロジェクトの作成と実行〜

この記事は,はてなエンジニア Advent Calendar 2017の12日目の記事です。 前日は id:cohalz さんによる 学生がエンジニアで仕事をして知識を増やすこと - cohаlz.hatеnablоg.сom でした。

概要

最近Common Lispで色々な実験をしています. Common Lispをはじめたばかりのころは,現代的な開発のやり方がよくわからずに苦労しました. 今となっては(まだわからないことだらけですが)ある程度やり方がわかってきたので, 主に,実用的な「じゃあこれからCommon Lispソフトウェアを作るぞ!!」といったときに使えそうなものをチョイスした備忘録を残そうとおもいます。

ASDFなどちょっと端折っている部分もありますが,ご容赦ください.

これを書いた人のレベル

  • Common Lispをはじめて1年くらいの社会人2年目エンジニア

処理系マネージャroswellを入れる

Common Lispにはたくさんの処理系がある.これらはインストール方法も異なるし,使い方も異なる.せっかく言語仕様が標準化されているのに,利用方法が異なるのはつらいことだ.できれば統一的に処理系を利用したい. Roswellはそういった事情で生まれた.RubyのrbenvやPerlのplenvのように,Roswellは適当なバージョンの適当な処理系をインストールし,管理し,呼び出してくれる. ついでにgo getのようなアプリケーションインストーラ的な機能もあるから,現代的な開発にうってつけの便利なツールだ.

インストール方法は https://github.com/roswell/roswell/wiki/Installation を手本として説明する.

まず開発用のlibcurlをインストールしておく.バージョンは3でも4でも動くので,好きな方をインストールしておくこと.

# 例: Ubuntuの場合
$ sudo apt install libcurl4-openssl-dev

OS Xの場合は,このままbrewを使えば完了する.

$ brew install roswell
$ ros setup

Linux/FreeBSDの場合,パッケージマネージャのレポジトリに登録されていることがある(linuxbrewやAUR,XBPS)が,登録されていないときはソースコードをビルドする. ビルドといっても難しくはない.GitとAutomake,標準的なビルドツールをインストールした状態で(CentOSではzlib-develもインストールして),次の手順に従う.

$ git clone -b release https://github.com/roswell/roswell.git
$ cd roswell
$ sh bootstrap
# ↓FreeBSDの場合は LDFLAGS="-L/usr/local/lib/" CFLAGS="-I/usr/local/include/" ./configure
$ ./configure
$ make
$ sudo make install
$ ros setup

Roswellのインストールができたら,好みの処理系を入れておく.

$ ros install ccl-bin/1.11
$ ros use ccl-bin/1.11
$ ros run # REPLを開く
Welcome to Clozure Common Lisp Version 1.11-r16635  (LinuxX8664)!

CCL is developed and maintained by Clozure Associates. For more information
about CCL visit http://ccl.clozure.com.  To enquire about Clozure's Common Lisp
consulting services e-mail info@clozure.com or visit http://www.clozure.com.

? ^D
? ^D
? ^D
$

Common Lispプロジェクトを作成する

cl-projectを使えば一発でプロジェクトが作成されます.プロジェクトを作成したい場所でREPLを開きます.

$ cd WHERE_PROJECT_WILL_BE_CREATED_TO
$ ros run
(ql:quickload :cl-project)
(cl-project:make-project #P"./hogeproject" :author "Windymelt")
writing ./hogeproject/hogeproject.asd
writing ./hogeproject/hogeproject-test.asd
writing ./hogeproject/README.org
writing ./hogeproject/README.markdown
writing ./hogeproject/.gitignore
writing ./hogeproject/src/hogeproject.lisp
writing ./hogeproject/t/hogeproject.lisp
$ tree hogeproject -a
hogeproject
├── .gitignore
├── README.markdown
├── README.org
├── hogeproject-test.asd
├── hogeproject.asd
├── src
│   └── hogeproject.lisp
└── t
    └── hogeproject.lisp

2 directories, 7 files

ソースとテストとREADMEとASDファイル,そして.gitignoreが作成されまし た. このまま開発することができます.

プロジェクトを実行する

コードは実行されるためにあるので,実行したいですよね. EmacsCommon Lisp開発環境であるSLIMEの使い方は別のエントリに任せるとして,ここではCLIからアプリケーションを起動する手順に注目します.

運用段階でよくある状況は,ある関数(main関数のような)をエントリポイントとして,処理系を実行するといったものです. これを行うスクリプトを作りましょう.

この場合に行わなければならないことは,

  1. 処理系の起動
  2. ライブラリの読み込み
  3. プロジェクトの読み込みとコンパイル
  4. 実行

です.けっこうやることが多いですね.

ここでは処理系ごとの差異を埋めるためにroswellを使います*1. Roswellは,上掲のうち,ライブラリの読み込み以外を行うことができます.ライブラリの読み込みは,ASDFやquicklispの仕事です.

プロジェクトを読み込む一般的なスクリプト

プロジェクトにこういうパッケージと関数があるとしましょう.

;;; src/hogeproject.lisp

(in-package :cl-user)
(defpackage hogeproject
  (:use :cl))
(in-package :hogeproject)

(defun hoge ()
  (format t "hello, hoge!~%"))

まずは大前提として,スクリプトから呼び出される側の関数をexportします. Common Lispのパッケージシステムでは,exportしていない関数はパッケージの外部から呼び出せません. ここではhogeproject:hogeexportします.

;;; src/hogeproject.lisp

(in-package :cl-user)
(defpackage hogeproject
  (:use :cl)
  (:export :hoge)) ;; exportしている
(in-package :hogeproject)

(defun hoge ()
  (format t "hello, hoge!~%"))

これでプロジェクト側の準備が整いました.これからこのプロジェクトを呼び出すスクリプトを作成します.

まずプロジェクトのどこか,たとえばscript/にroswellスクリプトを作成します.

$ mkdir script
$ cd script
$ ros init bootstrap
Successfully generated: bootstrap.ros

rosファイルが作成されました.これがroswellスクリプトで,これ自体に実行権限を与えるだけで実行できるようになっています*2

さて,プロジェクトを読み込ませるためには,ASDFがシステムを見付けられる必要があります.roswellには,ASDFがシステムを探す場所をオーバライドする機能があるので,これを使います. roswellのコマンドラインオプション-Sでこの機能を呼び出し,プロジェクトのASDファイルがある場所を読み込み元として指示します. rosファイルの冒頭にroswellを呼び出す箇所があるので,ここを修正しましょう.

#!/bin/sh
#|-*- mode:lisp -*-|#
#| <Put a one-line description here>
DN=$(dirname $0)
REPO=$(cd $DN/.. && pwd) # rosファイルの親ディレクトリにasdファイルがあるとき
exec ros -Q -S ${REPO} -- $0 "$@"
#           ^^^^^^^^^^ ここを追加した
|#

この状態でスクリプトを起動すると,ASDFがプロジェクトのシステムを発見できるようになり,quicklispで依存ライブラリを含めたロードができるようになります.

rosファイル中のquicklispにライブラリを読み込ませる箇所に,自分のプロジェクトを追加します.最終的にはこのようになります.

(progn ;;init forms
  (ros:ensure-asdf)
  #+quicklisp (ql:quickload '(:hogeproject) :silent t) ; hogeprojectが読み込まれる!!
  )

最後に,rosファイルでhogeproject:hogeを呼び出してみましょう.

(defun main (&rest argv)
  (declare (ignorable argv))
  (hogeproject:hoge))
$ script/bootstrap.ros
hello, hoge!
$

プロジェクトの関数を呼び出すスクリプトの実行ができました.めでたしめでたし. Roswellを一枚噛ませることで,処理系によらないプロジェクトのロードと実行ができるようになりました.

結び

Common Lispにも現代的なエコシステムが生まれつつあります。柔軟な言語を試してみてはいかがでしょう。

See other

require, ASDF, quicklispを正しく使う | κeenのHappy Hacκing Blog

github.com

www.asahi-net.or.jp

blog.8arrow.org

*1:Perlなどと違って,Common Lispには複数の処理系があります.

*2:shebangを使ってroswellを呼び出し,処理系が呼び出される仕組みです.