octahedron

LemとSKKとCommon Lispでたたかうプログラマのブログ

package-inferred-systemに期待したもの

これまでのあらすじ

 package-inferred-systemは、pakageのinferred-systemである。

 なんだか体がだるい感じのぼくはこころに学びを沁みわたらせるため、ライブラリのパッケージをすっきりと書くことはできないか考えてみようと思った。Common Lispを書いていてディレクトリ構成とパッケージ構造を同じにしたいとき、以下の2点が面倒だ:

  • パッケージ名をわざわざパッケージ定義に書くこと
  • パッケージ名とディレクトリ名を一致させること
    • パッケージ構造を変えたとき、変更が漏れやすい

 そこでぼくは、1ファイル1パッケージのスタイルでプログラミングできるらしいpackage-inferred-systemを試してみようと考えたのだった──

TL;DR

 package-inferred-systemは、ディレクトリ構造からパッケージ名を決定するためのものではない。

package-inferred-system とは

 package-inferred-systemは、Common LispASDF拡張機能です。ASDFCommon Lispにおいてライブラリ定義・読み込みを司る、ほぼ標準のアドオンです。Pythonでいえば、setup.py(定義)とdistutilsに相当します。それにしてもPythonにおいてモジュールパッケージは別のものを指す言葉だったのかー*1、知らなかったぜ 😎 。「ほぼ標準」というのは、Common LispANSIで言語仕様が定められていますが、ASDFはその中には含まれておらず、しかしだいたいの処理系に始めからバンドルされているので準標準的である、という意味です。

 ASDFの公式ドキュメントに拠れば、package-inferred-systemとは

Starting with release 3.1.2, ASDF supports a one-package-per-file style of programming, whereby each file is its own system, and dependencies are deduced from the defpackage form (or its variant uiop:define-package).

ASDF Manual: The package-inferred-system extension

とのことです。この機能に対して、誤った期待をしていたために、いろいろもやもやさせられた、というのがこの記事の要旨です。

期待していたもの

 ぼくがone-package-per-fileという言葉で期待したのは、Pythonのモジュールシステムのようなものでした。つまり、次に述べるようなものです。

 Pythonでは、ソースコードのファイル自体がモジュールです。なので、以下のようなa.pyb.pyがあるディレクトリでインタプリタを立ち上げて

# a.py
s = 's in module a'
# b.py
s = 'string in module b'
>>> import a
>>> a.s
's in module a'
>>> import b
>>> b.s
'string in module b'

ということができるようになる、と思っていました。つまり、Common Lispで次のようなことができるようになるのだ、と。

;;;; a/hoge.lisp
(defun foo ()
  (format t "No defpackage!!~%"))
CL-USER> (use-package :a/hoge)
CL-USER> (a/hoge:foo)
"No defpackage!!

 いや、exportしてないじゃん、という意見はごもっともですが。exportは手でする必要はあるにしろ、ディレクトリ構造と一致したdefpackageを書かなくなるなら、それは楽だなあと思っていました。

実際のpackage-inferred-system

 じゃあ、実際にはどうだったか。

 こうでした:ファイルパスと一致したdefpackageをしておけば、ASDF側でディレクトリ構造からパッケージ名を想像して読み込んでくれる。

 つまり、冒頭に挙げた問題点

  • パッケージ名をわざわざパッケージ定義に書くこと
  • パッケージ名とディレクトリ名を一致させること

は解決してくれません!! 残念!!!

 ただ、便利な点がまったくないかというとそういうことでもありません。以前ならば.asdフィアルに書いていた依存関係を、package-inferred-systemが内挿してくれるために、書かなくてよくなります。たとえばpackage-inferred-systemでない以下のようなsystemがあったときに、

;; hoge.asd
(defsystem hoge
  :depends-on (:alexandria)
  :components ((:module "src"
                :components
                 (:file "fuga")
                 (:file "core")
                 (:file "hoge"
                  :depends-on ("fuga" "core"))))

以下のように書くことで、:components節がまるまる不要になります。

;; hoge.asd (package-inferred-system)
(defsystem foo
  :depends-on ("alexandria"
               "hoge/hoge"))

 ただし、各パッケージに該当するファイルには、それぞれ以下のようなdefpackageをする必要があります(長くなるのでin-packageは省略しました)。

;; hoge/hoge.lisp
(defpackage :hoge/hoge
  (:use :cl
        :hoge/src/core
        :hoge/src/fuga))
(defpackage :hoge/src/fuga
  (:use :cl))
;; foo/src/core.lisp
(defpackage :hoge/src/core
  (:use :cl))

 つまり、ディレクトリ構造と:useなどからパッケージの依存関係を推測してくれるんだよ…!!

な、なんだってー!?

おわりに

 package-inferred-systemはone-pakcage-one-fileのスタイルを支援する機能です。しかしそれは、パッケージ名を自動で内挿してくれる機能ではありませんディレクトリ構造とインポートの記述からパッケージ依存関係を推測してくれる機能です。

 この機能の恩恵を特に得やすいのは、パッケージ数(=ファイル数)が多く、ディレクトリ構造が複雑なプロジェクトです。そういったプロジェクトは.asdファイルとディレクトリ構造を手動で一致させる手間が増えるため、defpackageさえちゃんとしていれば、依存関係を書かなくていいpackage-inferred-systemはかなり有用でしょう。

 こうして、package-inferred-systemへの溜飲が下がり、これからは使ってみようかなあという気になったのであった。

Lispエイリアン壁紙をつくりました2

 Lisp界のマスコット、Lispエイリアンの壁紙をまたつくりました。

http://f.hatena.ne.jp/t-sin/20170513011545f:id:t-sin:20170513011545p:plain オリジナルサイズ

背景

 以前Lispエイリアンの壁紙をつくりました。

octahedron.hatenablog.jp

 でも、黒いのがほしかったので、またつくりました。

 もっとクールなやつを、だれかつくってください!! だれか!!!

祝! Rosaバージョン1.0!

 最近つくったrosaというライブラリ・ツールのバージョンを1.0にしました。

github.com

 テキストデータに、書きやすく見た目を邪魔しない形式で、メタデータを埋め込むための言語です。実際にどんなものかというのは、以下の記事に書きました。

 名前は「ろさ」「ろーざ」のどちらかで読んでください。元ネタ的には「ろーざ」のようですが、ぼくは「ろさ」って読んでます。ラテン語

Ver. 1.0になって増えた機能

以下のことをしました:

  • ラベルには半角スペース以外の文字を使えるようになった
  • リスト省略表記

リスト省略表記

 リスト省略表記は、ラベルを並べてリストを表現するという方法のシンタックスシュガーです。以下の2コードが等しい感じです。

;; 略記じゃないベタ書き
CL-USER> (with-input-from-string (in "
:tag lisp
:tag common-lisp
:tag programmnig
")
           (rosa:peruse-as-plist in))
(:|tag| #("lisp" "common-lisp" "programmnig"))
;; リスト省略表記
CL-USER> (with-input-from-string (in "
:tag>
- lisp
- common-lisp
- programmnig
")
           (rosa:peruse-as-plist in))
(:|tag| #("lisp" "common-lisp" "programmnig"))

 これだけです。

以下、回想

 アイデア自体は2011年ごろからあり、今でいう静的サイトジェネレータの記事データを表現するために考えたものでした。当時はまだLispは興味があるだけだったので、rosaをC++で実装しようした形跡があり、手元の旧プロジェクトのコミットログにはそれが残っています。就職してからCommon Lispで実装しだして一旦完成、そのあとに謎の軽量マークアップ言語機能が乗ったりしたあと放置。

 なんとか実行可能バイナリをつくったこともありました。Common Lispの実行可能バイナリのビルト方法がよくわかっていなかったので、その時偶然できたバイナリをわりと最近まで使い続けてました。

 マークアップ言語部分は、あおぞら文庫の構文や組版本を参考にしつつ、シュッとした言語になればいいなーと妄想しています。

github.com

 苦節もうすぐ七、八年か。考えた当時(大学生!)はCommon Lispでこんなにコード書いてるとは思いませんでした。ゲームでもつくんだろうなって思ってました。どうしてこうなったのか。

 これからも誰の役に立つのかよくわからないプログラムをCommon Lispで書いていけたらいいなあー。

Seriesで響け!ユーフォニアム!

 Series触る触ると言ってたのに触ってなかったので、手始めに響けユーフォニアムをseriesでやってみました。

2017/4/29 追記

「妙なwarningが出てしまうこと」について、以下の記事の反応をいただきました。

Seriesの色々 — #:g1

この記事ではapplyでseriesをつくっているために、seriesの最適化のしくみにこの記事のコードを載せることができなかったのが原因なのでしょう。

Seriesとは

 Seriesは、Common Lispに導入されかけたけど時間がないので却下されたという、繰り返し構造を宣言的に表現するためのしくみです。Seriesを使えばHaskellClojureのような、関数型言語っぽいデータ処理ができそうな感じです。

 仕様は"Common Lisp the Language 2nd Edition"の付録Aに書かれています。英語ならウェブでも読めます。

 また日本語の説明は次のページが詳しいです。

 ちなみに使うときはこんな感じです。

CL-USER> (ql:quickload :series)
CL-USER> (use-package :series)
CL-USER> (collect 'string (subseries (apply #'series (coerce "グソクムシ" 'list)) 0 20))
"グソクムシグソクムシグソクムシグソクムシ"

響け!ユーフォニアムとは

 アニメの名前で、ぼくは見ていませんが、ちょっと前にtwitterでこんな遊びがはやっていました。

 その時期にぼくもCommon Lispformat関数でやってみたりしたので、とりあえずseriesの入門としてやってみるのにいいのではと思った次第です。

 redditlisp_jaが、似た問題でちょっと盛り上がってた時期がありました。

結果…

 Seriesのままならこんなかんじ:

CL-USER> (let* ((str "響け!ユーフォニアム")
                (len (length str)))
           (mapping ((i (scan-range :from 0 :by 1 :upto len)))
             (collect 'string
                      (subseries (apply #'series (coerce str 'list)) i (+ i len)))))
#Z("響け!ユーフォニアム" "け!ユーフォニアム響" "!ユーフォニアム響け" "ユーフォニアム響け!" "ーフォニアム響け!ユ" "フォニアム響け!ユー" "ォニアム響け!ユーフ" "ニアム響け!ユーフォ" "アム響け!ユーフォニ" "ム響け!ユーフォニア" "響け!ユーフォニアム")

 問題に沿って標準出力に出力するならこうか:

CL-USER> (let* ((str "響け!ユーフォニアム")
                (len (length str)))
           (iterate ((i (scan-range :from 0 :by 1 :upto len)))
              (format t "~a~%"
                      (collect 'string
                        (subseries (apply #'series (coerce str 'list)) i (+ i len))))))
響け!ユーフォニアム
け!ユーフォニアム響
!ユーフォニアム響け
ユーフォニアム響け!
ーフォニアム響け!ユ
フォニアム響け!ユー
ォニアム響け!ユーフ
ニアム響け!ユーフォ
アム響け!ユーフォニ
ム響け!ユーフォニア
響け!ユーフォニアム
NIL

 関数型っぽくシーケンス処理を書けるので、カッコいいと感じました。これからも使っていきたいです。

疑問

 上のコード、実行するたびに以下のようなwarningが出るんですよね。なんとかならないものか。

; in: LET* ((STR "響け!ユーフォニアム") (LEN (LENGTH STR)))
;     (ITERATE ((I (SCAN-RANGE :FROM 0 :BY 1 :UPTO LEN)))
;              (FORMAT T "~a~%"
;                      (COLLECT 'STRING
;                               (SUBSERIES (APPLY #'SERIES #) I (+ I LEN)))))
; 
; caught WARNING:
;   Warning 28 in series expression:
;   (COLLECT 'STRING (SUBSERIES (APPLY #'SERIES (COERCE STR 'LIST)) I (+ I LEN)))
;   Non-series to series data flow from:
;   (APPLY #'SERIES (COERCE STR 'LIST))
;   to:
;   (SUBSERIES (APPLY #'SERIES (COERCE STR 'LIST)) I (+ I LEN))
; 
; compilation unit finished
;   caught 1 WARNING condition

Rosaに謎のシリアライズ機能が登場

 先日こんなものが完成しました。

octahedron.hatenablog.jp

 まあ用途や有用性の不明な、なんだかよくわからんプロジェクトだとぼく自身も思うわけですが、なんなんでしょうね(ぼく自身ではわりと使ってます)。

Rosaについて

Rosaとは

 文字列に名前を付けて、それをプレーンテキストで表現できる言語です。そして、それをパースするライブラリ・コマンドでもあります。

 機能の弱いJSONとかYAML、のような感じと見做すこともできるかもしれません。
 「タイトル」「著者名」「更新日付」「本文」「あとがき」などのテキスト情報をひとつのファイルに、人間が書きやすい構文(=インデントや妙な括弧がない)で書き表せる言語です。小説とかブログの一記事とか、そういうのを一ファイルで書くのに使おうと思ってつくりました。

 ちなみに、中のテキストのマークアップは感知しないので、別の言語(Markdown、reStructured text、あおぞら文庫形式)を選択できます。というかしてください。

Rosaでパースしたテキストデータ

 Rosaで定められた形式で、key-value的なデータを表すことができます。たとえば、ポケモンとその登場作品の対応、とか。

CL-USER> (setf pokemon "
:diamond-pearl ブイゼル
:sun-moon モクロー
:sun-moon クワガノン")
CL-USER> (with-input-from-string (in pokemon)
           (rosa:peruse-as-plist in))
(:|diamond-pearl| #("ブイゼル")
 :|sun-moon| #("モクロー" "クワガノン"))

テキストをパースすると、rosa:peruseでハッシュテーブル、rosa:peruse-as-plistでプロパティリストになります。これから所望のデータを抜き出して、wc -mするもよし、grep -n hogeするもよしです。

謎のシリアライズ機能 (new!)

 それでとりあえず何が増えたのかというと、ハッシュテーブルやプロパティリストを与えると、それを表現するrosaの文字列に変換する関数が増えました!

CL-USER> (with-input-from-string (in pokemon)
           (rosa:peruse in))
#<HASH-TABLE :TEST EQL :COUNT 2 {1002956813}>
CL-USER> (with-input-from-string (in pokemon)
           (rosa:indite (rosa:peruse in)))
":diamond-pearl ブイゼル
:sun-moon モクロー
:sun-moon クワガノン
"

 それだけです。やったぜ。

 いったいどんなユースケースあるのかは謎ですが、おもしろかったので実装しました。

おもしろいなと思ったこと

 peruseinditeの対が、マセマティカル・モルフォロジーにおける膨張と侵食の関数みたいに、随伴っぽい関係になっているなあ、とふと気づきました。

 まとまっていないし、検証も証明もされていないけど、おもしろげであると思ったことをざっと列挙

  • peruseinditeを使うと、二つの領域(文字列、ハッシュテーブル)を行き来できる
    • peruseすると、文字列はkey-valueなデータに写される
    • inditeすると、key-valueなデータはrosaの言語に写される
  • (indite (peruse in))という関数(モルフォロジの閉包作用素っぽい)を考えると、これは冪等っぽい
  • どんなテキストファイルでもperuseによりrosaの言語の世界に引き込むことができる
    • あとはinditeperuseで二つの領域をぐるぐるする

 なんとなく類似を感じた概念等はこちらの本の6章を参照のこと: 『非線形画像・信号処理  (モルフォロジの基礎と応用)』。

おわりに

 大学で学んだ抽象的な概念がこのような形で姿を見せるとは、学問とはおもしろいものだなあと思った。

Rosa --- メタデータ付きテキストを表現する言語

Rosa

 Rosaは、プレーンテキストにタイトルや作成者などのメタデータを付与するための、メタなマークアップ言語です。また、そのパーサライブラリであり、パーサコマンドの名前でもあります。

 ちなみにCommon Lispで実装しました!!!

github.com

動機

  • ムサシ「何だかんだと聞かれたら」
  • コジロウ「聞かせてあげよう我らが名を」

ロケット団 (アニメポケットモンスター), 口上, 5代目(SM編)- Wikipedia

 不躾ながらも文章やらなんやらを書くのが趣味なのです。そして書いた文章は、題名や書いた日付や、その他脚注やメモや参考にしたURLなんぞといっしょに同じテキストファイルに残しているわけなんです。そういう付加的な情報はファイルの中に書いておきたい*1

 でも、そんな記法・言語ってあったっけ?

 マークアップ言語の中で候補を考えると、XMLMarkdown、reStructuredText、JSONYAMLなどなどいろいろありますが、以下の理由でそれぞれ却下です:

  • XML … あの構文はプレーンテキストと相性が悪い
  • Markdown … 構文は軽いが、名前を任意に埋め込むのは範疇ではない
  • reStructuredText … 構文は軽いが、マークアップ部分がオーバースペック
  • JSON … 名前とデータを記述するのにはいいが、プレーンテキストにブラケットはちょっと…
  • YAMLJSONよりはいいけど、プレーンテキストにインデントはちょっと…

 そもそも、前述の言語は文書構造を表現するものたちであって、付加的な情報を表現するものではないのです。探した限りで、そういう言語は見当たりませんでした。

──そんな言語がないのなら、つくるしかないじゃない! あなたも! わたしも…!!

<2017-03-24追記>

 このrosaを、つくっておきながらジャンルがよくわからなかったとき、Masaiさんからメタマークアップ言語では?と教えていただきました。ありがとうございます!

で、どんな言語?

 というわけでつくったのがrosaでございます。

 どんな言語か紹介します。まず、見た目でいうとこんな感じ。

:title あのときの王子くん
:author アントワーヌ・ド・サン=テグジュペリ
:source-site あおぞら文庫
:source-url http://www.aozora.gr.jp/cards/001265/files/46817_24670.html

:body

〈星から出るのに、その子はわたり鳥をつかったんだとおもう。〉

[#改ページ]


レオン・ウェルトに

 子どものみなさん、ゆるしてください。ぼくはこの本をひとりのおとなのひとにささげます。でもちゃんとしたわけがあるのです。そのおとなのひとは、ぼくのせかいでいちばんの友だちなんです。それにそのひとはなんでもわかるひとで、子どもの本もわかります。しかも、そのひとはいまフランスにいて、さむいなか、おなかをへらしてくるしんでいます。心のささえがいるのです。まだいいわけがほしいのなら、このひともまえは子どもだったので、ぼくはその子どもにこの本をささげることにします。おとなはだれでも、もとは子どもですよね。(みんな、そのことをわすれますけど。)じゃあ、ささげるひとをこう書きなおしましょう。

...

 Rosaのファイルはテキストデータメタデータから成ります。テキストデータに付けた名前(メタデータ)を、:から始まる行で表現します。メタデータの名前のことをrosaのREADMEの中ではラベルラベルと呼称しているので、以降もラベルということにします。

 一行だけのテキストデータと、改行を含むテキストデータで書き方がすこし違います。

インラインラベル

 一行のほうは、:の次から最初のスペースまでがラベルです(インラインといいます)。例を示すと、

:hoge-inline Common Lispがすきです

こうです。二つ目以降のスペースはテキストデータの一部になります(ちなみにラベルは[a-z][a-z-]+正規表現じゃなければ、ふつうの行だと認識されます)。

ブロックラベル

 複数行のほうは、:からスペースなしの改行までがラベルになります(ブロックといいます)。対応するテキストデータの範囲は、次の行から、その次のラベルかファイル終端までです。例示すると、

:hoge-block

Commmon Lispが最高に好きです。
Emacsも好きです。

:fuga-inline モクローも好きです。

こう。

コメント&エスケープシーケンス

 あと、ブロックラベルには、複数行コメント(行頭の;はじまり)があったり、:;に対するエスケープシーケンスがあったりします:

:body

; この行は無視されます
:; この行はセミコロンから始まります
:: この行はコロンから始まります

 以上がrosaの構文です。

 ちなみに、同名ラベルが複数あったとき、その本体はちゃんと出現順で保持されています。

ライブラリ使用法

 Common Lispのライブラリなので、$ ros install t-sin/rosaで導入でき、REPLで(ql:quickload :rosa)するとすぐに使用できます。

パースする

 パースする関数はperuseです。Rosaの入力はストリームで渡してください。結果はハッシュテーブルで返ってきます。

(with-input-from-string (in ":label hogehoge")
  (rosa:peruse in))
; => #<hash-table ...>

 もしすぐに結果が見たければ、peruse-as-plistを使うとplistで結果が返ってきます。

コマンドライン使用法

 だんだんめんどくさくなってきたので簡単に。

 機能としては以下の三つのことができます:

  1. 入力をパースして、ラベルのリストを出力する
  2. 入力をパースして、指定したラベルのテキストを出力する
  3. 入力をパースして、入力中の全ラベル - テキストを出力する

 それぞれ出力の形式をオプションで選べます。

 詳細はREADMEを見てください。

ラベル一蘭を出す

$ cat hoge.txt | rosa index
ラベルが
ズラーっと
出る

指定したラベルの内容を出す

$ cat hoge.txt | rosa pick title -j
"hogeタイトル"

全構造を出す

$ cat hoge.txt | rosa pick title -y
# YAMLで全構造がずらーっとでる

そのほかのユースケース

 テキストファイルのメタデータを表現する以外にも、ラベル - テキストの構造がkey-valueっぽいので、エスケープシーケンスを駆使すれば、デキスト版key-value storeとして使えるような気がしました。非効率だけど。

 あと、仕事では一度この使い方をしたことがあるのですが、ウェブAPIのフォームの値をテキストファイルにrosaの形式で書いておけば、それを読み込むプログラムを(Common Lispで)つくりやすかったです。ある種key-value storeとして使ったとも言える。

今後の発展

 いまは以下のようなのをぼんやり考えています:

  • quicklispに登録しちゃう!?
  • パフォーマンス測定
    • 巨大ファイルだいじょうぶ?
    • 比較対象あれば(だれか教えて!)
  • ラベルの文字数制限する?
  • テストの実装言語非依存化?
  • 別の言語で実装??

 あと、趣味を改善するプロジェクトとして次は、日本語の文章を表現するためのマークアップ言語hearnを考え中なので、こちらもやらねば。

github.com

*1:タイトルやいつ書いたかなどは、ファイル名やパスやファイル更新日時で表現は可能です。でも、気分で変更することもあるので、それらに依存してほしくない。それがファイルの内容に残しておきたい理由です。

Lispエイリアン壁紙をつくりました

TL;DR

Lisp界のマスコット、Lispエイリアンの壁紙をつくりました。

同日 19:45追記; オリジナル画像がダウンロードできませんでした。笑止。

f:id:t-sin:20160429103312j:plain オリジナルサイズ 壁紙そのいち

f:id:t-sin:20160429103306j:plain オリジナルサイズ 壁紙そのに

動機

Lispを愛するならば、デスクトップの壁紙もLispにしたいと思うのはわりと自然な発想だと思います。そうしてLispの壁紙をググってみるわけです

少ない。

ピンとくるのがない。

ところで、Clojureにはなかなかカッコいい壁紙があるようです。公式でロゴがあるからでしょうか。羨しいですね。

Lisp界には、Lispエイリアンというマスコットがおります。あの愛らしい緑色のアイツです。目がいっぱいの。彼(彼女)の壁紙があれば、オールオッケーなのではないか。そう考えた次第であります。

ググってみる

ないぢゃん!!

なんでだよ!!

Why Japanese people!?

結論

というわけで、壁紙を(半年くらい前に)つくりました。ずっと自分で利用するだけだったんですが、つくったものを公開することは(プログラムでなくても)Lisp界への貢献になるのかな、と思った次第で、それで公開することにしました。先駆けになって、もっといいものが出てくると信じて…。

もっといい壁紙がほしいです。

だれかつくってください。