curryは飲み物

概要

LISPでカリー化された関数を定義するマクロを作ってみた.カリー化された関数は,必要な数の引数が渡されればただちに評価されて結果を返すが,引数の数が足りなければ部分適用された関数となる.それもまたカリー化されている.

動機

昨日も一日中椅子に座ってJava書いて……今日も一日中椅子に座ってJava書いて……きっと来週も再来週も一日中椅子に座ってJava書いて……毎日毎日同じ事の繰り返しで,生きてる気がしないんだYO!

簡単なカリー化

定義通りカリー化するだけなら,こんなふうにすればいい.

(defun currying (arg-list &rest body)
  (let* ((args (reverse arg-list))
         (last-arg (car args))
         (other-args (cdr args)))
    (reduce #'(lambda (acc elem) `(lambda (,elem) ,acc)) other-args
            :initial-value `(lambda (,last-arg) ,@body))))

(currying '(a b c) '(+ a b c))
; => (LAMBDA (A) (LAMBDA (B) (LAMBDA (C) (+ A B C))))

(defmacro defun/curry% (name arg-list &body body)
  (let ((first-arg (car arg-list)))
    `(defun ,name (,first-arg)
       ,(apply #'currying (cdr arg-list) body))))

(macroexpand-1 '(defun/curry% sample-fun (a b c) (+ a b c)))
; => (DEFUN SAMPLE-FUN (A) (LAMBDA (B) (LAMBDA (C) (+ A B C))))

ちょっとかっこいいカリー化

簡単なカリー化の問題

呼ぶのが超めんどくさい.

(funcall (funcall (sample-fun 1) 2) 3)

やっぱLISPカッコ多いっすねwwwwwなどと言われてしまう.本来はこのように呼びたい.

(sample-fun 1 2)
; => #<部分適用された関数.あと1つの引数で評価される>

(funcall (sample-fun 1 2) 3)
; => 6

(sample-fun 1 2 3)
;; => 6
部分適用された関数を表すオブジェクトを作る

部分適用された関数は状態を持つ関数であると考える.それは次の状態を持つ.

  • これまで与えられた引数
  • あとどれだけ引数が足りないか
  • 引数が足りた時に呼ばれる関数

構造体で表すとこう.

(defstruct curried
  args-luck
  args
  fun
  )

;; 3引数の関数.与えた引数はなし.
(defvar *curried-fun*
  (make-curried :args-luck 3 :args NIL
                :fun #'(lambda (a b c) (+ 1 2 3))))
部分適用された関数オブジェクトを呼ぶ

この部分適用された関数オブジェクトはただの構造体なので,関数のようには呼べない.呼ぶための関数は次のように書ける.

(defun call-curried (curried-fun args)
  "部分適用された関数と,引数のリストを取る関数"
  (cond
    ;; 引数がなければ,任意の引数を取って関数を呼ぶ関数を返す
    ((null args) (lambda (&rest args) (call-curried curried-fun args)))
    ;; 足りない引数が残り1つなら,関数本体を呼び出す
    ((= 1 (curried-args-luck curried-fun))
     (apply (curried-fun curried-fun) (nreverse (cons (car args) (curried-args curried-fun)))))
    ;; それ以外なら,引数リストに値を一つプッシュして,残り引数のカウントを減らす
    (t (call-curried (make-curried :args-luck (- (curried-args-luck curried-fun) 1)
                                   :args (cons (car args) (curried-args curried-fun))
                                   :fun  (curried-fun curried-fun))
                     (cdr args)))))
マクロでラップする

いちいち構造体を定義するのは難儀だし,引数の数をいちいち数えるのもかったるい.定義時に引数の数はわかるので,マクロでラップできる.

(defmacro defun/currying (name arg-list &body body)
  (let ((args (gensym))
        (curf (gensym)))
    `(defun ,name (&rest ,args)
       (let ((,curf (make-curried :args-luck ,(length arg-list) :args NIL
                                  :fun #'(lambda ,arg-list ,@body))))
         (call-curried ,curf ,args)))))

呼び出すときはこう.

(defun/currying add3 (a b c)
  (+ a b c))

(add3 1 2)
; => #<CLOSURE (LAMBDA (&REST ARGS) :IN CALL-CURRIED) {25B69F85}>

(funcall (add3 1 2) 3)
; => 6

(add3 1 2 3)
; => 6

まとめ

curryは飲み物

LISPで中置記法を書くマクロ作ってみた

中置記法もありじゃないかな

LISPといえば前置記法。前置記法といえばLISP。曖昧さのない前置記法+カッコという形式は、わかりやすいし生成しやすいし、大変便利です。LISPファンのかたの中には、中置記法なんていらねえよ! という方もおられるでしょうが、時には中置記法のほうがスッキリ書ける場合もあるでしょう。

て言うのもですね、Twitter中置記法マクロを見たんですよ。

infixing
https://github.com/pasberth/infixing

あー確かに中置記法マクロを定義するマクロあったら便利かもわからんね、と思って、じゃあちょっとコードを読んでみましょう、と思ったんですが。

                /|:::::::::::::::::::::ヽ.:.:.:.:、:.:.:.:、:.:.:.、.:.、.:.:.:.:.:.::`゛>
           /{::|:\:::::::\.:.:.:\.:.:.ヽ::.::.ヽ:.:.ヽ::::::::::.:.`゛ー- ..,__
: 何 :    /:|::',:ト、::::::ヽ、:.\:.:.:.\:.:.ヽ:.:.:\.:.:.:.:.:::.:.:.:.:::.::::_;:-'´   : : :
: が :   //:/:::|::',|::'、:::::::::\:.:\.:.:.ヽ:.:.:\:.:..\::::::::::::\、::::\    : : :
: 何 :  /!::|::l::::/|:::l:ヽ:\::ヽ:.:\:.:\.:::ヽ:.:.:ヽ:.:.:.:\::::::::::::\ ̄   : : :
: だ :   |/l::|::|::|:ト、:::::::::、、:ヽ、:.:.:.:::::::::::::::ヽ::::.:ヽ:.:.:.:.\:.:.:.ヽ:::\.   : : :
: か :   |::|::/l::|::|r‐ヽ:::::ヽ(ヽー,―\::::::、::::::::::ヽ::.:.::::::.:::::::ヾ. ̄   : : :
:    :   }//l::|:::|{(:::)ヾ、:::ヽ \!(:::) ヽ,:::ヽ:::::::::::::::::::::::::::::::::::ヾ、   : : :
: わ :.  |/l::|::|:::|ヽ==''" \:ヽ、ヽ=='" |:::::::::::::::::::::::::::::::::::ヽ、::::\
  か     / ',|::|:::|   /   `゛       |!::::::::::::::::::::::::::::ト、::ト、_` ゛`
  ら      l::!::::ト、  '、 _         ||::::::::::::::::::::::::ト:ヽヾ| | ̄ ̄ ̄`ヽ、
  な     r'"´||',::::',                 |:::::/l:::::|\:::ト、ヾ | |     / / \
  い   /   ll ',::', 、 ーこニ=-       /!::/ ヽ:::|  ヾ、  ノ ノ  /  ,イ   ヽ、
       ,'    |  '、:, \ --       ,. '´ |;'  l ヾ、.   //     / |    l: l
      |   |!  ヽ;  ヽ       /.:    i!  /   ゛// |l      / |      | |


ちょっと私のLISP*1が足りなくてわけがわからなかったので、勉強しようと思って自分で書くことにしました。

操車場アルゴリズム

わたくし、Racc*2は使ったことがあるのですが、恥ずかしながら中置記法の優先度を考慮して正しい構文木に直す方法が分からなくて……

ぐぐってみたら見つかりました。

操車場アルゴリズム(そうしゃじょうあるごりずむ)は、計算機科学において、中置記法の数式を構文解析する技法である。逆ポーランド記法 (RPN) または抽象構文木 (AST) の形式で出力を生成するのに使える。このアルゴリズムエドガー・ダイクストラが考案したもので、鉄道の操車場に似た操作をするため、このような名称がつけられた。
http://ja.wikipedia.org/wiki/操車場アルゴリズム

LISPマクロにするにあたっての簡略化

  • 括弧による優先順位は無視します。カッコがあったらそれはS式です
  • 指定された演算子以外は全部数値とみなします。S式も数値です
  • エラーチェックはしません(値と演算子が交互に来ることを確認しないといけないはず)

というわけで実装

(defvar *op-list*
  '(
    (^ :right 10)
    (* :right 5)
    (/ :right 5)
    (+ :right 1)
    (- :right 1)
    )
  "演算子の定義。演算子に続くのは、結合の方向と優先度。優先度は大きいほど高い")

(defun op-p (op? op-list)
  "あるオブジェクトがシンボルであるかを試す述語"
  (assoc op? op-list))

(defun get-weight (op op-list)
  "ある演算子の優先度を取得するための関数"
  (third (assoc op op-list)))

(defun get-assoc (op op-list)
  "ある演算子の結合の方向を取得するための関数"
  (second (assoc op op-list)))

(defun op-comp (comp op another-op op-list)
  "演算子同士の大小を comp によって比較する"
  (let ((op-weight      (third (assoc op         op-list)))
        (another-weight (third (assoc another-op op-list))))
    (funcall comp op-weight another-weight)))

(defun op< (op another-op op-list)
  "演算子opの優先度が、別の演算子another-opのそれより小さければ真を返す"
  (when (null another-op) (return-from op< t))
  (op-comp #'< op another-op op-list))

(defun op<= (op another-op op-list)
  "演算子opの優先度が、別の演算子another-opのそれと等しいか、より小さければ真を返す"
  (when (null another-op) (return-from op<= t))
  (op-comp #'<= op another-op op-list))

(defun left-associative-p (op op-list)
  "演算子opが左結合ならば真を返す"
  (eq :left (get-assoc op op-list)))


(defun soshajo (source op-list &optional stack q)
  "操車場アルゴリズム。sourceに中置記法で書かれた式を、op-listに演算子の定義を渡す"
  (if (null source) ; 式が終わった場合
      (nconc (nreverse stack) q) ; スタックが空になるまで中身をキューにプッシュ
      (let ((new-op      (car source))
            (op-in-stack (car stack)))
        (cond ((not (op-p (car source) op-list))
               ;; sourceの先頭が演算子でないなら、それをすぐ出力キューに入れる
               (soshajo (cdr source) op-list stack (cons new-op q)))
              ((and op-in-stack
                    (or (and (left-associative-p new-op op-list)
                             (op<= new-op op-in-stack op-list))
                        (op< new-op op-in-stack op-list)))
               ;; sourceの先頭にある演算子が左結合でスタックの一番上のものより優先度が等しいか低い場合
               ;; あるいはスタックの一番上のものより優先度が低い場合
               ;; スタック上の演算子をポップし、キューに入れる
               (soshajo source op-list (cdr stack) (cons op-in-stack q)))
              (t
               ;; いずれでもなければ、演算子をスタックに入れる
               (soshajo (cdr source) op-list (cons new-op stack) q))))))


(defun rpn->sexp (rpn-lst stack op-list)
  "逆ポーランド記法をS式に直す処理"
  (if (null rpn-lst)
      (car stack)
      (let ((elem (car rpn-lst)))
        (if (op-p elem op-list)
            (let ((arg1 (first  stack))
                  (arg2 (second stack)))
              (rpn->sexp (cdr rpn-lst) (cons (list elem arg2 arg1) (cddr stack)) op-list))
            (rpn->sexp (cdr rpn-lst) (cons elem stack) op-list)))))

(defmacro infixing (rule sexp)
  (rpn->sexp (nreverse (soshajo sexp NIL NIL rule)) NIL rule))

(macroexpand-1 `(infixing ,*op-list* (1 + 2 ^ 3 ^ 4))) ;=> (+ 1 (^ 2 (^ 3 4)))

ね? 簡単でしょ?

まとめ

*1:LISPを読み書きする力。私のLISP力はせいぜい5といったところかな

*2:スゴイ級ハッカー青木氏によって書かれたRubyのパーサジェネレータ

ぷよm@s感想・今後の展開予想とか

前説

ぷよm@s面白いですよね。

ぷよm@sとは、初代ぷよぷよでアイドルたちが激突するアイマス架空戦記(?)です。

Part1

最新(2012/12/16現在)

彼女らが遊ぶ初代ぷよぷよは、その後のシリーズにない特徴があります。それは、「おじゃまぷよ相殺がないため、先に致死連鎖(おじゃまぷよを72個以上送る連鎖)を組み終わったほうが勝つ」ということ。初代ぷよでは、一回の勝負が一瞬で決するのです。そのため、このシリーズのバトルには大変なスピード感と緊張感があります。
そこにさらに、最速最大効率の『連鎖法』は何か? というバトルの面白さ、誰がどの『連鎖法』を選択するのか? という異能バトル・流派バトル的なキャラ立てが加わります。基盤となるバトルのバランスはよくとれていて、最強のキャラがいるけど諸事情で本筋に絡まないという小細工もありますが、基本的には初代ぷよのルールの範囲内で実力が拮抗していて、それぞれの連鎖法に利点欠点がある。そのためにバトルが単調にならず、連鎖法の選択でキャラが引き立つ。タラPの知識とやりこみがなせるワザです。
このような要素が、ぷよm@sを見て面白い神シリーズにしているのですが、さらに平行して公開されている『ぷよぷよ講座』を見たり、初代ぷよぷよ風ネット対戦ツール『Bぷよ』を使うことで、初代ぷよを実際に遊んで楽しむこともできます。

やよいと学ぶぷよぷよ講座

Bぷよ
http://bx0.digick.jp/puyo/

実に素晴らしい動画シリーズですね。これはもう見るしかない。

と言うよりむしろ……

            『初代ぷよを楽しむことを……強いられているんだ!』

\            /: : : : : :ハl\; : ト、: : : : : : : : : :.:\  /    /
  \          /: : : : : :>   \l \ト、: : : : : : : : \     /
           /^ヽ: : : :/         \ト、: : : : : : :\  /
\ \       / ヘ |: : :/              \: : : : : : /、         _  -‐ ´
    \.    / /ノ: :/    ヘ      |        `\:\ーヽ    -‐'"´
       ___j  |/: ノ    __\.   |   ./       /:;\i
       |: : :.| . /:.:/     ´ tテ‐≧__ノ   /       //´
       /: : : し': :/         ̄_>,  r≦___、  /
       \: .ソ厶/              /<tj ヽ. ′ノ         -‐‐‐‐ "´
      _/^7 .//           / \ ` ´ /
     //レ′{ /        、   /      /
____/  |.  ヽ/  ._ -‐ _  - ´     ノ
   /  八 l  \   |^l  ヽ ` -、_     ./          `゙ ー- 、_
    {    \    '、 \\__ 〉/   /       \          `゙ ー- _
    |      ヽ    ヽ\`‐---‐'´ ,. ィ´}           \
    |        \   \  ̄   / / .|\     \  \

ということで、すっかりぷよm@sファンなのです。

真! 俺だ! 嫁にもらってくれ! とかいう感想を並べるのもいいですが、キャラの立ち位置まとめとかまとめたくなったので、それに今後の予想を入れて感想としておきます。

キャラ立て

キャラの戦略は大雑把に致死二連鎖−致死多連鎖−駆け引きと分けられますが、それが基本としている連鎖で分けて、デスタワー−究極連鎖法−ズラース法−カウンター−ヘルファイア基軸とすると、ループ状になっています。

戦略 基礎とする連鎖 使用者
二連鎖 ヘルファイア 春香
二連鎖 デスタワー やよい
二連鎖 デスタワー 美希
多連鎖 究極連鎖法 千早
多連鎖 究極連鎖法 律子
多連鎖 ズラース法 小鳥
多連鎖 ズラース法 亜美
多連鎖 ズラース法 真美
多連鎖 ズラース法 伊織
多連鎖 カウンター あずさ
駆引き カウンター
駆引き ヘルファイア 雪歩

ループ状だからなんなんだと言われても困りますが、なにやら綺麗に収まって来たので、クライマックス感あるなーと思っています。こういうのは最強を決めるより、全員のキャラが立ったところが終わりどきかなと思うので。

また、その他の情報も入れた表として並べてみると、戦略・連鎖法がばらついていること、連鎖法が同じでも、強みと弱点がそれぞれ違うことがはっきりわかります。

戦略 連鎖法 備考・その他のスキル 使用者 弱点 残っているフラグ
致死二連鎖 定形2連鎖マルチ:ヘルファイア 本式。ヘルファイアB可能 春香 決定的な武器がない 新型ヘルファイア
致死二連鎖 定形2連鎖マルチ:デスタワー 本式。対応力が高い 美希 特になし 対クイック潰し。ランキング戦復帰。961プロの2人
致死二連鎖 定形2連鎖マルチ:デスタワー 略式 やよい 不安定 ですたわー完成
致死多連鎖 不定形4連鎖ダブル 5連鎖可能。凝視可能。対応力が高い 小鳥 特になし アイドルデビュー
致死多連鎖 不定形5連鎖:究極連鎖法 タラ式。4ダブ可能 千早 つぶしに弱い 敗北後の展開
致死多連鎖 不定形5連鎖:究極連鎖法 秋月式。4ダブ可能。凝視可能。対応力が高い 律子 特になし あずさとの勝負
致死多連鎖 定形5連鎖:ズラース法 4ダブ可能。まわしが上手い 亜美・真美 かませポジ 姉妹の確執
致死多連鎖 不定形5連鎖 多くの引き出しを持つ あずさ 致命的に遅い 次は誰の師匠に?
潰し 不定形非致死連鎖:ペチペチ法 5連鎖も組めなくはない 伊織 致命的に遅い さらなる上位への挑戦
二択 不定形5連鎖:カウンター 凝視可能 カウンター形では遅い 勝戦
速攻潰し・掘り合い 定形2連鎖マルチ:ヘルファイア 略式。ヘルファイアA可能。掘りが異様に強い。凝視可能。クイック使用 雪歩 特になし 真との関係

雪歩強すぎワロタ。

残ったフラグはあんまり多くありませんね。ほんとにそろそろクライマックスなのでしょうか。

戦法の進化

回を追うごとに進化していく戦法も楽しみのひとつですが、進化にも方向性がありますよね。
1つはクラシックな連鎖から、不定形を入れて最速・最大効率を目指す、いわば正統派、千早の打倒小鳥さんスタイル。
もう1つは編み出した新戦法でクラシックな戦法を脅かす、美希・雪歩のごぼう抜きスタイル。さらにそれにアドリブを入れて磨きをかけています。
そして、新戦法とクラシックスタイルの合わせ技で二択を迫る真スタイル。

イマイチ見せ場のない亜美真美と春香さんはどうなるのでしょうか。

そんなわけで今後の展開予想

  • Pや社長が戦う場面は見られないんじゃないかな。
  • 亜美真美のバトルはあると思います。新戦法出るかどうか。
  • 春香さんがヘルB踏襲からさらにもう一段覚醒して主人公エンドになると思う。
  • 伊織は……伊織はきっとがんばってくれる。やよいと互角ぐらいになる。
  • 小鳥さんアイドルデビューと同時に30に。

とはいえ今まである情報のみ+ぷよ素人の予想なので、新たな戦法で961の2人が参戦してくるとかはあるかもですね。パートは108まで行くのか。楽しみです。

asdfをはじめよう(Getting started with ASDF)を訳したった

(拙い翻訳で)すまんな。

ASDF日本語の関連情報(その他にもあったら教えて欲しいなって)

#9LISP How to use ASDF
http://beta-reduction.blogspot.jp/2010/05/9lisp-how-to-use-asdf.html

Common Lisp users jp ASDF
http://cl.cddddr.org/index.cgi?ASDF

原文

"Getting started with ASDF"
Mario S. Mommer

Last modified: April 05, 2006.
http://common-lisp.net/~mmommer/asdf-howto.shtml



*** ここから翻訳 ***


Introduction

この文書の目的 (Purpose of this document)

この文書の狙いは、ASDF(Another System Definition Facility)を使うための短い導入をすることです。深遠なトリックやtips、ASDFそれ自身やシステム定義ツール一般のデザインについては議論しません。

The aim of this document is to give a brief introduction to the use of ASDF, Another System Definition Facility. It is not about the arcane tricks and tips, and is not about the design of ASDF itself nor about system definition tools in general.

システム定義ファイルとは、ソースファイル間の依存関係の記述に過ぎません。それによって、ソースファイルは正しい順番でコンパイルされ、ロードされます。A.lispとB.lispというファイルがあったとして、もしB.lispが、A.lispで使われた定義とコードのどちらか(あるいはどちらも)を含んでいたならば、A.lispはB.lispに依存しています。
*1

A system definition file is nothing else than a description of dependencies between files of source code in such a way that they can be compiled and loaded in the right order. A file A.lisp depends on a file B.lisp if the latter contains definitions and/or code that is used by A.lisp.
*2

この問題を解決することは、ほとんどのケースにおいてとても簡単です。そのため、ごく小さなスクリプトをハックする分には大丈夫です。しかしながら、正しいツールを学ぶことは、将来、大きな時間の節約になるでしょう。この小さなチュートリアルで、我々はシンプルな事例と、よく知られたツールであるASDFの一般的な使い方に集中します。このツールのより進んだ特徴については、 ASDF manual に任せることにしましょう。

Solving this problem is in the vast majority of cases fairly trivial, so hacking up some minimal script will usually work. Learning the right tool, however, will save you a lot of time down the road. In this minitutorial we will concentrate on the simple cases and on the general usage of a fairly well known tool, ASDF. We leave the more advanced features of this tool to the ASDF manual2.

ASDFはどんな問題を解決しますか? (What problem does ASDF solve?)

あなたがインターネッツ、あるいは他の何かから、ソフトウェアのソースコードをダウンロードした時、あなたは普通、不定形のバッチファイルではなく、特定の方法で相互に依存しあうコンポーネントからなるシステムを手にしているはずです。その帰結として、もしあなたがそのソフトウェアを(ライブラリとか、アプリケーションとするために)ビルドしたいとき、あなたはおそらくそのコンポーネントを順番に、ひょっとしたらいくつかのものには特別な注意をしながら、順番にビルドしていくことになるでしょう。開発者がそういうことについてすべて面倒をみてくれていて、ビルドプロセスを進めるのにたったひとつコマンドを入力すればいいだけなら、あなたとても幸福ですね?

When you download the source code of some software from the Internet, or get it from some other source, you usually do not get an amorphous bunch of files, but instead get a system of components that depend on each other in some particular way. The consequence of this is that, if you want to build the software (be it a library, or be it an application), you will probably have to build these components, and the components of these components in order, perhaps giving some special treatment to some of them. You would, of course, be very grateful if the developer had prepared everything, and you could trigger the build process by a single command.

あなたがもし少ないコンポーネントからなるプロジェクトで働いている開発者ならば、1つのコンポーネントを変更したら、それが、影響を受けるコンポーネントだけを再コンパイルして再ロードしてくれるような、コンポーネントの依存関係を整理してくれる仕組みが欲しいと思っているかもしれません。

If you are a developer working in a project with a few components, you will probably want some mechanism that keeps track of the dependencies between these components, so that if you change one component, triggering a rebuild only recompiles and reloads the components which are affected.

最後に、あなたは多分、コンポーネントの依存関係とビルドとロードのプロセスをどーにかしてくれる一貫したシステムが欲しいんじゃないでしょうか。
そうすれば、みんながソフトウェアをインストールしたり使ったりする時間を省いてくれますからね。

Finally, you probably want a consistent way of dealing with the dependencies between components and of building and loading software systems, simply because it saves everyone time when installing software and using software.

ASDFは大雑把に言って、ソフトウェアコンポーネントの依存関係を定義し、結果として起こるビルドプロセスの細かいことを規定する、拡張可能なシステムです。そして、ASDFはとても広く使われているので、あなたのシステムについて、多くの人が理解できるだろう、と期待できます。

ASDF is, roughly speaking, an extensible facility for defining the dependencies between software components, and specifying eventual details of the build process. It is also in fairly wide use, so that you can assume that your system definition will be understood by many others.

同じ事が、ファンも多ければアンチも多い mk:defsystem についても言えますが、我々はASDFにのみ集中します。どこからかはじめなければいけないからです。どんなものでも、2つの実際の違いはパワーユーザーにしか分かりません。

The same can be said about mk:defsystem, which has fans as well as detractors. We will only concentrate here on ASDF, since we have to start somewhere. In any case, the actual differences between these two only become apparent for the power user.

法律の問題 (Legal matters)

Creative Commons License
This work is licensed under a Creative Commons Attribution 2.5 License.

ASDFでシステムを定義する (Defining systems with ASDF)

ASDFのシステム定義は .asd という拡張子のついたファイルに収められます。話を明確にするために、ある、何かでしゃばらない名前の、そう、 cow というプロジェクトのためにシステム定義を書きたいものとしましょう。

An ASDF system definition is stored in a file with the extension .asd. To make the discussion clearer, we are going to pretend that we want to write the system definition facility for our software project, called, unassumingly, cow.

システム定義ファイルは cow.asd にするべきでしょう。そして、少なくとも大体の場合で、あなたのソースコードがあるのと同じディレクトリに置くべきです。emacsを使っているなら、 cow.asd のはじめの行には次のように書くでしょう。

The system definition file should be called cow.asd, and, at least in the usual scenarios, should reside in the same directory as your source code. If you use emacs, you might want to put the following as the first line in cow.asd.

;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-

これで適切なシンタックスサポートをオンにできます。コレが必要なのは、emacsはデフォルトでは .asd ファイルに関して必要なことを知らないからです。

This makes sure that the proper syntax support is turned on, which is necessary because by default emacs knows nothing about .asd files.

この後、 cow.asd は次のようなコードで始まることになります ( 前後にコメントを伴って……言うまでもないですね )

After that, cow.asd should start with the following code (preceded or followed perhaps by comments; goes almost without saying)

(defpackage #:cow-asd
  (:use :cl :asdf))

(in-package :cow-asd)

次に書くべきは、いくつかの(オプショナルな)特別な情報を伴った defsystem フォームです。

The next thing is to write a defsystem form, together with some (optional) extra information.

(defsystem cow
  :name "cow"
  :version "0.0.0"
  :maintainer "T. God"
  :author "Desmon Table"
  :licence "BSD sans advertising clause (see file COPYING for details)"
  :description "Cow"
  :long-description "Lisp implementation of our favorite ruminant"

予想しているだろう通り、最初の行は必須です。

As you might have guessed, only the first line is mandatory.

基本形 (The base case)

もっとも単純なケースでは、あなたはごく少ないファイルが入ったディレクトリを1つだけ持っていることになるでしょう。そして、さらに疑いようもなく単純なケースというのは、ファイルの依存関係が線形な時です。この場合、あなたははじめにロードされるファイルがはじめに、次にロードされるファイルが2番めに……といったリストを作れます。

In the simplest case you have a directory with a few files. And the absolutely simplest case is when the dependency of your files is linear. That is, you can make a list of the files such that the first one should be loaded first, the second second, etc.

そういうケースの場合、defsystemフォームはこんな感じになります。

In such a case, the defsystem form can look like this.

(defsystem cow
  ;;; (Optional items omitted)
  :serial t ;; the dependencies are linear.
  :components ((:file "defpackage")
               (:file "legs")
               (:file "tail")
               (:file "head")))
複雑な場合 (A more complex example)

我々は、すでに述べたのと同じファイルを持っているものと思います。しかし、依存関係をより精確に定義したいと思っているでしょう。すべてのファイルが依存する、 defpackage.lisp と呼ばれるファイルがありますそして何か不思議な理由で、 tail.lisp が leg.lisp に依存しています。そんなプロジェクトの defsystem フォームは次のようになります。

Suppose that we have the same files as before, but would like to specify the dependencies more accurately. There is a file called defpackage.lisp, from which everything else depends. And we have that, for some mysterious reason, tail.lisp depends on legs.lisp. The defsystem form for this project could look as follows.

  ;;; (Optional items omitted)
  :components ((:file "tail"
                      :depends-on ("package" "legs"))
               (:file "legs"
                      :depends-on ("package"))
               (:file "head"
                      :depends-on ("package"))
               (:file "package")))

このケースでは、 cow.asd をソースファイルと同じフォルダに置いています。もしもあなたが我々の幸福な cow システムを試したいなら、関連した節へと直接飛んでください。

In this case you would keep the file cow.asd in the same directory as the source files. If you want to try out our nice cow system, you can jump directly to the pertinent section.

モジュールのあるシステム (A system with modules)

我々は次のような、もっと入り組んだ構造を持っていることでしょう。 head.lisp とか legs.lisp があります.また我々は、サブシステムとしていくつかのファイルを含む respiratory (訳注:呼吸器) というサブシステムをもっており、breathing というサブディレクトリに入っています(cow.asdがあるディレクトリのサブディレクトリです)。同様に、 circulation(訳注:循環器;恋愛は関係ない)というサブシステムもあります。
defsystemフォームは多かれ少なかれこんな感じになります。

Suppose that we have the following, more involved structure. We have a head.lisp, and legs.lisp. But we also have a subsystem called respiratory, which consists of a few more files, and lives in its own subdirectory called breathing (this is a subdirectory of the directory where cow.asd lives). Similarly, we have another subsystem called circulation. The defsystem form could look more or less like this.

(defsystem cow
  :components ((:file "head" :depends-on ("package"))
               (:file "tail" :depends-on ("package"
                                           circulation))
               (:file "package")
               (:module circulation
                        :components ((:file "water"
                                            :depends-on
                                            "package")
                                     (:file "assorted-solids"
                                            :depends-on
                                            "package")
                                     (:file "package")))
               (:module respiratory
                        :pathname "breathing"
                        :components (...))))

circulation モジュールにあるファイルとは circulation サブディレクトリにあるすべてのファイルです。そのため、circulation モジュール package.lisp は、それより上のレベルにある同名のそれとは違うものです。

Note that the files in the module circulation all live in the subdirectory circulation. Thus the file package.lisp in the module circulation is a different one than that a level higher.

モジュールはコンポーネントとしてファイルとモジュールの両方を持てます。さらにそのモジュールもコンポーネントとしてファイルとモジュールを持てます。依存性は所与のコンポーネント集合の中でのみ定義されることは、特筆すべきことです。ですから、 tail.lisp はサブモジュールのコンポーネントである assorted-solids には依存できません。

A module can have as components both files and other modules, which in turn can have files and modules as components. It is important to note that dependencies can only be defined inside a given set of components. So, the file tail.lisp cannot depend on the file assorted-solids, which is a component of a submodule.


他のシステムに依存する (A system that depends on other systems)

他のシステムに依存しているシステムもいつもの場合と同じですが、 :depends-on オプションが違っています。こうなります。

A system that depends on other systems will look exactly like a regular one, except for an additional :depends-on option. It looks like this.

(defsystem cow
  ;;; ...
  :components (...)
  :depends-on ("other-system"))

システム定義ファイルをどう使うか (How to use system definition files)

ソフトウェアがあるのと関連したディレクトリの中に、システム定義ファイルはあります。しかし、ソフトウェアをビルドしてロードするのにそのディレクトリをワーキングディレクトリにする必要はありません。システム定義ファイルへのシンボリックリンクを、 asdf が探すディレクトリに置いておきさえすればよいのです。

System definition files live in the directory where the corresponding piece of software lives. However, you do not need to have that directory as your working directory to be able to build and load said software. You only need to put a symbolic link to the system definition file in a directory where ASDF searches.

普通のセットアップは次のようになります。まず、 asdf をロードする必要があります。幾つかの実装では、 asdf はすでにロードされています。
それ以外では、書いてあるとおりです。

The usual setup is as follows. To begin with, you need to have ASDF loaded. In some implementations ASDF is already loadad. In others, it is just a matter of writing

  (require 'asdf)

一方、幾つかの実装では、あなたははじめに、 asdf をインストールする必要があります。ここからファイルを取得できるので、適当な場所に置いてください。例えば、 foo という名前でログインしているなら、 /home/foo/lisp/utils/ にです。コンパイルしたいかもしれません。

whereas in others you might need to install it yourself first. You can get it, in a single file, from here, and put it somewhere reasonable. For instance, if your login name was foo, in the directory /home/foo/lisp/utils/. You may also want to compile that file.

そして、Common Lisp処理系の初期化ファイルのどこかに(CMUCLなら、ホームディレクトリにある .cmucl-init.lisp )、次のような感じの一節を書きます。

Now, somewhere in the init file of your Common Lisp implementation (for CMUCL it would be .cmucl-init.lisp in your home directory) a variation of the following passage should appear.

(load "/home/foo/lisp/utils/asdf")

(setf asdf:*central-registry*
   ;; Default directories, usually just the ``current directory''
  '(*default-pathname-defaults*

    ;; Additional places where ASDF can find
    ;; system definition files
    #p"/home/foo/lisp/systems/"
    #p"/usr/share/common-lisp/systems/"))

cow システムをビルドし、ロードするためのコマンドは、次のようなものです。

The command to build and load system cow is

(asdf:operate 'asdf:load-op 'cow)

cow.asd がカレントワーキングディレクトリにあるならば、ビルドとロードのプロセスはそこで始まります。そうでないならば、 asdf は中央レジストリにあるディレクトリから cow.asd という名前のシステム定義ファイル、またはそれへのシンボリックリンクを見つけ出します。後者が見つかった場合、
オリジナルのファイルへのリンクをたどり、そのディレクトリでビルドプロセスを走らせます。ファイル自体が見つかれば、見つけたディレクトリでビルドプロセスを走らせます。

If the file cow.asd happens to be in the current working directory, the build and load process will start there. If not, ASDF will search through the directories in the central registry, and look for a system definition file named cow.asd, or for a symbolic link to one. If it finds the latter, it will follow the link to the original file, and run the build process in the corresponding directory. If it finds the file, it will run the build process in the directory where it finds it.

ですから、もしあなたが /home/foo/lisp/systems/ に cow システム定義ファイルのシンボリックリンクをんな感じで置いたとして……

So, if you make a symbolic link in /home/foo/lisp/systems/ to the cow system definition file, by executing (for example)

$ cd <where-your-system-defs-are>
$ ln -s /home/foo/code/cow/cow.asd

……cow ソフトウェアのビルドとロードは、そのファイルが置いてあるディレクトリに行くことなく、次のようなコマンドで可能になります。

Then you can build and load the cow software without having to be in the directory where this software lives simply by issuing the command

(asdf:operate 'asdf:load-op 'cow)

*1: LISPにおいては、事態は少し複雑になります。しかし、我々はもっともよくあるケースに限ることにしましょう。そのためには、この定義で十分です

*2: In lisp, things can get a little more complicated. We limit ourselves here to the most common cases, and for them this definition is good enough.

AWKっぽいコードをLISPで書く

__________
    <○√
     ‖ 
     くく
しまった! これは車輪の再発明だ!
ココは俺に任せて先に http://www.cliki.net/CLAWK に行け!

こんな感じでテキストファイルを処理するLISPユーティリティが欲しい。

(defawk parsing
    ;; letと同じ変数束縛。全てに先んじて束縛される。
    ;; 続くすべての節で、ここで定義した変数を使える。
    (sum)

  ;; レコードに対する繰り返しの前に実行される begin節
  (begin (setf sum 0))

  ;; レコードに対して毎回繰り返し実行される body節
  (body
   ;; body節の中はどんなLISPコードでもよい
   ;; ただし、matchではじまるリスト(match節)は、カレントレコードに対する
   ;; 正規表現のマッチを試し、マッチすれば実行されるようなコードとなる。
   (match "(\\w+)"
     ;; マッチ結果は $ 関数で参照できる
     (format t "MATCH: ~A in ~A~%" ($ 1) ($ 0))))
  (body
   ;; body節の処理中は、フィールドセパレータによって区切られた
   ;; フィールドを、 $f 関数で参照できる
   (format t "RECORD: ~A~%" ($f 0))
   (print *current-fields*)
   (print *number-of-fields*)
   (incf sum (read-from-string ($f 3))))

  ;; すべてのレコードに対する処理が終わった後に実行される end節
  (end (format t "TOTAL: ~A~%" sum)))

(何をしてるコードかはあんまり重要じゃない)

ということで書いてみた。

ちなみに、CL-PPCREに依存している。

;;; 設定できるオプション
(defvar *field-separator* "\\s+"
  "フィールドセパレーター。デフォルトでは、1つ以上の空白文字")


;;; *** マクロの中で使われる関数とスペシャル変数 ***

;;; それぞれの行を処理するときに、カレントのレコード、
;;; それが何番目のレコード化、分割されたフィールドのリスト
;;; そしてフィールドの数を記録しておくスペシャル変数。
(defvar *current-record* "")

(defvar *current-fields* nil)

(defvar *number-of-records* 0)

(defvar *number-of-fields* 0)

(defun update-record (record)
  "レコードを引数にして、上記のスペシャル変数を更新する関数"
  (setf *current-record*
	record
	*number-of-records* (1+ *number-of-records*)
	*current-fields*
	(cl-ppcre:split *field-separator* record))
  (setf *number-of-fields* (length *current-fields*)))


;;; レコードに対して正規表現でマッチをしようとしたときに、
;;; その結果、各レジスタを保持するリスト、レジスタの長さの
;;; 3つを記録しておくためのスペシャル変数
(defvar *matched* "")

(defvar *match-list* NIL)

(defvar *number-of-matches* 0)

(defun match (regexp)
  "引数として与えられた正規表現のマッチを試し、
その結果を上記のスペシャル変数にセットする"
  (multiple-value-bind (matched match-list)
      (cl-ppcre:scan-to-strings regexp *current-record*)
      (let ((len (length *match-list*)))
	(setf *matched* matched
	      *match-list* match-list
	      *number-of-matches* len))))


;;; *** ここからマクロを展開するための関数 ***

(defun check-clauses (clause-list)
  "各節が健全な切かどうかを試す関数
具体的には、それがbeginかbodyかendではじまるリストなら健全"
  (mapc (lambda (clause)
	  (unless (find (car clause) '(begin body end))
	    (error
	     "各節は begin, body, end のいずれかではじまるべき: ~S"
	     clause)))
	clause-list)
      t)


(defun pickup-clauses (sym clause-list)
  "節のリストの中から、ある種類の節だけを抜き出す

節はリストなので、第一引数で表されたシンボルではじまる節をすべて集め、
そのシンボルだけを取り除いた残り(つまり、cdr)を、まとめて返す。
別々の節に書かれたコードも、書かれた順に1つにまとめられる"
  (apply #'append
	 (mapcar #'rest
		 (remove-if-not
		  (lambda (clause) (eql sym (car clause)))
		  clause-list))))

(defun parse-body-clause (clause)
  "body節をパースする。
もしそれがmatchではじまるリストならば、下の関数で書き換える"
  (case (car clause)
    (match (match-clause-to-sexp clause))
    (t clause)))


(defun match-clause-to-sexp (clause)
  "matchではじまるリストを、match関数の呼び出しに書き換える"
  (destructuring-bind (match-sym regexp . body) clause
    (declare (ignore match-sym))
    `(progn (match ,regexp)
	    (when *matched*
	      ,@body))))


;;; fn-nameという名前の関数を定義するマクロ。
;;; この関数は、関数を1つ受け取り、節で定めた処理を実行していく。
;;; 渡される関数は、引数なしの関数であり、呼び出すとレコードとして
;;; 文字列を返すか、もうレコードが無いことを表すnilを返すべきである。
(defmacro defawk (fn-name binding-clause &rest clause-list)
  ;; まず、適切な節だけを含むか確認する
  (check-clauses clause-list)
  (let ((record-supplier (gensym "RECORD-SUPPLIER"))
	(record (gensym "RECORD"))
	(begin-clause-list (pickup-clauses 'begin clause-list))
	(body-clause-list (pickup-clauses 'body clause-list))
	(end-clause-list (pickup-clauses 'end clause-list)))
    `(defun ,fn-name (,record-supplier)
       (let ,binding-clause
	 (labels (;;; フィールドを参照するための関数。
		  ;;; ($f n) でn番目のフィールドを参照する。
		  ;;; フィールドのカウントは1からはじまる。
		  ;;; ($f 0) はカレントレコードと等しい
		  ($f (n)
		    (cond ((or (not (integerp n))
			       (minusp n)
			       (< *number-of-fields* n))
			   NIL)
			  ((zerop n) *current-record*)
			  (t (nth (- n 1) *current-fields*))))
		  ;;; match節によるマッチの結果を参照するための変数
		  ;;; ($ 0) はマッチした全体を表す文字列を返す
		  ;;; ($ 1) 以降は部分マッチのレジスタを参照する
		  ($ (n)
		    (cond ((or (not (integerp n))
			       (minusp n)
			       (< *number-of-matches* n))
			   NIL)
			  ((zerop n) *matched*)
			  (t (svref *match-list* (- n 1))))))
	   ;; begin節はここに展開される
	   ,@begin-clause-list
	   ;; レコード供給関数がnilを返すまで、body節を繰り返し実行
	   (do ((,record (funcall ,record-supplier)
			 (funcall ,record-supplier)))
	       ((null ,record) nil)
	     (update-record ,record)
	     ,@(mapcar #'parse-body-clause body-clause-list))
	   ;; end節は繰り返しの後に展開される
	   ,@end-clause-list)))))

このように使う

;;; レコードを供給する関数……を生成する関数を定義
;;; この場合は単にファイルから一行ずつ読むだけ。すべて読むと行の代わりにNILを返す。
(defun make-record-supplier (in)
  (lambda ()
    (read-line in NIL)))

;;; 処理を定義。これによってparsing関数が作られる。
(defawk parsing (sum) ; 合計を記録していくための変数 sum を宣言
  (begin (setf sum 0)) ; sum を 0 にしておく。別に宣言時に ((sum 0)) としても同じ事

  ;; \w+ を含むレコードがあればこの節が実行される
  (body
   (match "(\\w+)"
     (format t "MATCH: ~A in ~A~%" ($ 1) ($ 0))))

  ;; すべてのレコードについて、分割されたフィールドとかを表示してみる
  (body
   (format t "RECORD: ~A~%" ($f 0))
   (print *current-fields*)
   (print *number-of-fields*)
   (incf sum (read-from-string ($f 3)))) ; 3番目のフィールドの数字を sum に累積していく

  ;; すべてのレコードを読み終わったら sum の値を表示
  (end (format t "TOTAL: ~A~%" sum)))

;;; レコード供給関数生成関数を parsing 関数に渡してやると処理が始まる
(with-open-file (in "result.txt")
  (parsing (make-record-supplier in)))

設計という作業をみんなでやるのは難しい系の話

この記事を読んで思ったこと.結論とかはない.

ソフトウェア開発プロセス残酷物語
http://junichiito.hateblo.jp/entry/2012/08/26/181015


工場でのライン作業は,する方は単純作業だが,その指示を出す方は色々な工夫をしている.

ひとつは,人間という,行える動作の種類については柔軟だが,その出力や可動範囲,連続使用可能時間については頑なな精密作業用機械を,最大限活用するための工夫だ.

工具や部品は,体を曲げ,手を伸ばして取る位置にではなく,すぐ取れる位置に置けるようにする.そうしないと時間がかかるし疲れるからだ.

一日の作業時間は限定し,必ず休憩を入れる.あまりに疲れていると作業の能率も精度も落ちるからだ.

また,ラインの効率と組み換えの容易さのトレードオフも考慮しなくてはいけない.

少ない品種を大量生産する必要があるなら,動作や配置の細かい最適化をしたり,大きな機械を据え付けることになる.効率は上がるが,そのぶん,ラインの変更は難しくなる.

大量の品種を少しずつ生産する必要がある場合,ラインを切り替える手間のほうがネックになるので,専門の大型機械などは据え付けられない.ラインの組み換えという作業に対する最適化をかけ,動作は後回しにする.

あるいは製品をそもそもコンポーネント化する.コンポーネントを組み合わせる動作は共通だけれど,そのときどきでつかむコンポーネントは異なる,というようにする.

これは無駄が多いが,変更に強い.

プログラムを書くのは,生産ラインでいえば,単純作業ではない.ラインを設計する仕事だ.プログラムとは作業の指示書であり,機械や部品,工具のレイアウト指示書だ.

しかもこの生産ラインは極めて複雑であり,しばしば変化する.車の生産ラインを設計していた人が,また明日も車の生産ラインを設計しているとは限らないというようなものだ.

もちろん,似たような生産ラインを作るにあたってはノウハウが蓄積されていく.生産ラインが,業種は違えど,コンベア式の直線的なラインの上で,部分部分を作っていく方式と,ひとりひとつの作業台でひとつの製品を組み上げる方式と……というように分類できるように,ソフトウェアもいくつかのアーキテクチャに分類できる……あるいはそれを頭において設計すべきだ,とされる.

どのラインにも必須なネジのようなコードは,ネジに規格品があるように,ライブラリとして整理されて,それがライン設計に道筋をあたえてくれる.

考慮すべき要件も整理されている.変更の頻度と程度は? 求められている効率は? 工場を立てるときに使える敷地はどれくらいだろう? 入れられる機械は? その値段は? また,それをどう考慮し,どう意思決定したか,ドキュメントとして残すべきだ,ということもわかってきている.

生産ラインのレイアウトの制約条件を自動検証してくれるシステムもある.要するにテストと型チェックだ.

そのような知識を使えば,ある程度まで,生産ラインの設計も自動化できるかもしれない.

しかし,まだ整理されていない,職人芸的な部分はかなり残っており,それを再現できる職人の数も足りていない,ということなのだろう.



追記:

元記事で書かれていたのは,職人を育てるにはどうしたらいいか,職人同士を効率的に協調させるにはどうしたらいいかという話と,作業指示書が雑すぎて読めないという話だろう.

それをどうしたらいいのかは寡聞にして聞いたことがない.

コピペ改変〜ある大学の授業〜

ある大学でこんな授業があったという。
「クイズの時間だ」教授はそう言って、大きな壺を取り出し教壇に置いた。
その壺に、彼は一つ一つ岩を詰めた。
壺がいっぱいになるまで岩を詰めて、彼は学生に聞いた。
「この壺は満杯か?」教室中の学生が「はい」と答えた。
「本当に?」
そう言いながら教授は、教壇の下からバケツいっぱいの砂利をとり出した。
そしてじゃりを壺の中に流し込み、壺を振りながら、岩と岩の間を砂利で埋めていく。
そしてもう一度聞いた。
「この壺は満杯か?」学生は答えられない。
一人の生徒が「多分違うだろう」と答えた。
教授は「そうだ」と笑い、今度は教壇の陰から砂の入ったバケツを取り出した。
それを岩と砂利の隙間に流し込んだ後、三度目の質問を投げかけた。
「この壺はこれでいっぱいになったか?」
学生は声を揃えて、「いや」と答えた。
教授は水差しを取り出し、壺の縁までなみなみと注いだ。彼は学生に最後の質問を投げかける。
「僕が何を言いたいのかわかるだろうか」
一人の学生が手を挙げた。
「砂利や砂や水はあとからでも入れられる.だから、壺の中には”大きな岩”を先に入れるべきだということ、
つまり、限られた時間の中では重要なことを先にやれってことです」
「その通りだ。しかし……」と教授は言って、ハンマーを取り出し、壺を叩き割った。
「君たちはまだ若い。こざかしい最適化をするよりも、大きな器を探しなさい。
小さな器にせこせこ詰め込むことを考えるくらいなら、器を叩き割ってしまうべきだ。
大学の授業はそのためにあるのだよ」