女帝をもっとも知らない女帝アドベントカレンダー参加者から見た女帝
はじめに
4月
11月
一体何が起こったのだろうか……。自分でもよく分からない。ところで、このツイートの「寄稿」は誤用ですね。申し訳ない。ちなみにLisp Advent Calendar 2012の原稿は書き上げて期日通りに公開しました。Garbage Collection Advent Calendar 2012 - Adventarの原稿はまだ終わってません。参加登録は終えているのであとは書くだけですね。書くだけですね!書くだけですね……。
これは女帝 Advent Calendar 2012の17日目の記事として書かれた、女帝こと[twitter:@yamashitam]とあまり接点のない私がなぜ女帝を認識し、私からTLでよく見かける女帝がどう見えているのか、というとりとめのない文章なので、何か有用だったり興味深かったりClojureに詳しくなったりすることはないと思われます。Clojureで書かれた女帝絡みのアプリケーションも含まれていません。また、タイトルは実在するGC本の人(Richard Jonesではない)、カーネルハッカーとか欧州貴族とかメイドとかガチャピン先生とか呼ばれていて正直よくわからない人、最年少コミッタ記録保持者(2012年12月現在)とは一切関係ありません。ご了承ください。
mikutterと女帝のこと
[twitter:@toshi_a]という人がいます。私はこの人をいつの間にかフォローしていて、いつフォローしたのか本当によく覚えていないのですが、この人がよくツイートする「mikutter」という単語が何を意味するのかはよくわかっていませんでした。しかし、それはこの人が書いたTwitterクライアントだと気づいた時、私はmikutterに興味を持ち、mikutterと関係のありそうな人を何人かフォローするようになりました。その頃からTLに女帝という単語が登場する頻度が急増したような気がします。それ以前から女帝という単語は見かけていたのですが、最近話題になった高飛車なアイドルの愛称か何かだと思って特に気にしていませんでした。
しかし、女帝という日常会話にはあまり登場しない単語が続けて何度も流れてくる上、フォローしている人の中でもプログラミングをしている人からよく言及されていることに気づくと、次第に「ひょっとしたらこれは私にとって注目すべきキーワードではないか」と思うようになりました。そこで調べたのが今年の春だったと思います。女帝とはソフトウェアの名前ではなく人の愛称であること、Twitterにポストしていて、[twitter:@yamashitam]がそのアカウントであることはその頃理解しました。
しかし、その時の私にはその人が何か特別な人には見えませんでした。よく覚えていませんが、最新ツイートの話題が食事や移動などだったんだと思います。もし「卒研でコンパイル時OCRに取り組んでいる」とか、「言語処理系を作ろう系の本は電卓とか作らせてないで、まずRubyのparse.yを読ませればいいと思う」とか、「なんでみんなのパソコンはXeonじゃないの?」とか書いてあれば反応も違っていたと思いますが、そういう分かりやすいタイプの人ではありませんでした。
情報科学若手の会と女帝のこと
情報科学若手の会というイベントがあります。この会は情報処理学会が開催するプログラミングシンポジウムの一つです。参加したことは一度もありませんが、フォローしている人の話を読む限り、意欲次第で多くを学ぶ機会になる印象があります。この会に学生がよく参加する大学とそうでない大学があり、私はどちらかと言うと後者の大学に通っているのですが、できたら在学中に参加したいと思っています。
夏休み中、ケータイでTwitterを見ているとフォローしている人、していない人、その人が書いたコードを読んだことのある人、ない人、様々な人が#wakate2012ハッシュタグでツイートしていました。その中でアナウンスしたり発言を紹介したり突っ込んだりしていたのが[twitter:@yamashitam]でした。幹事の一人として運営に参加していたそうで、女帝のふぁぼ爆撃で会場中(のスマホ)が震えているとか女帝が兄を「コンテンツ力がない」と罵倒しているとか不穏なツイートも目にしましたがすごく活動的な人なんだろうと思ったのはよく覚えています。
私から見た女帝のこと
そろそろまとめなければならないのですが、正直なところよくわかりません。情報系で、よく誰かと交流していて、学ぶ意思がしっかりした人という印象がありますが、なぜ女帝と呼ばれているのかわかりません。調べてみたら本人もわからないそうなのでずっとわからないままかもしれません。あと、大らかな人といった感じの評価を目にしたことがあります。コンテンツ力のダメ出しが厳しいといった感じの評価を目にしたこともあります。どういうことなんでしょうか……?
女帝のことは結局よくわかりませんでしたが、このイベントに投稿された他の文章を読むと、様々な人に親しまれ、あるいは慕われているように見えます。私には遠い存在のようにも思えますが、遠いと思っているのも私が心理的に壁を作っているだけですし、まあつまり来年は私もより活動的になりたいですね。
では、私はまたPyPyのコードに潜ります。これを読んでいるあなたも来年はPyPyのコードを読んでみるのはいかがでしょう。あるいは名前はどこかで見たけど使ったことはないプログラミング言語に挑戦する、気になっていたけど手を付けていない分野を学び始めるというのもいいかもしれませんね。 :-)
あなたが良い新年を迎えますように。
mikutterのアーカイブでxz圧縮の効果を検証する
3日連続でコードの解説を書いた反動でブログスイッチが切れてました。しかも今回はClojureとまったく関係ありません。ごめんなさい。
xzとは
LZMA系の圧縮アルゴリズムを使い、XZ Utilsのコマンド(Windowsでも動く)がgzipやbzip2のコマンドのように動くのでUnixな人々にも優しいファイルフォーマットです。詳しくはThe .xz file formatを参照してください。
一般に次のような特徴があると言われています。
つまり、圧縮頻度より展開頻度が多い*2圧縮ファイルに向いています。例えば、……ソースコードのアーカイブとか。
今回はmikutter 0.0.3.540を対象に、xzの効果を検証します。
実験に使うハードウェアとソフトウェア
実験
配布されているアーカイブを展開してdu -bした所、圧縮前のサイズは約6.42MB(6420229バイト)でした。
最初はtarコマンドのオプションで圧縮して比較します。
コマンド | 圧縮形式 | 時間 | サイズ | 圧縮率 |
---|---|---|---|---|
tar zcf | gzip | 0.422秒 | 2.01MB(2013972バイト) | 31.4% |
tar jcf | bzip2 | 2.874秒 | 1.64MB(1643739バイト) | 25.6% |
tar Jcf | xz | 4.788秒 | 1.41MB(1412920バイト) | 22.0% |
gzipの圧倒的な速度が目を引きます。xzはgzipの約12倍の時間をかけ、約600KBを削減しています。
しかし、xzには-e/--extremeという、速度を犠牲にして更に圧縮率を高める(かもしれない)オプションがあります。また、man xzには次のような事が書かれています。
- gzip(1)やbzip2(1)と同じ感覚でなんでもかんでも-9で圧縮するのは良い考えではない。
- 時々、-0はgzip -9より速く、圧縮率が良い。
- デフォルトの-6は16MiBしかメモリがないようなシステムで展開されるファイルにも使える、一般に良い選択肢。
というわけで、次はtarコマンドで圧縮はせずに、gzip -9、bzip2 -9、xz -0、xz -6、xz -9、xz -9eを使って圧縮して、展開はtarコマンドのオプション指定で計測します。
圧縮コマンド | 圧縮時間 | サイズ | 圧縮率 | 展開時間 |
---|---|---|---|---|
gzip -9 | 0.824秒 | 2.01MB(2006397バイト) | 31.3% | 0.158秒 |
bzip2 -9 | 2.899秒 | 1.64MB(1643739バイト) | 25.6% | 0.650秒 |
xz -0 | 1.384秒 | 1.57MB(1571568バイト) | 24.5% | 0.387秒 |
xz -6 | 4.746秒 | 1.41MB(1412920バイト) | 22.0% | 0.388秒 |
xz -9 | 4.830秒 | 1.41MB(1412920バイト) | 22.0% | 0.355秒 |
xz -9e | 7.816秒 | 1.42MB(1420480バイト) | 22.8% | 0.452秒 |
今回は-eがうまくいかないデータだったようで、時間は極端に(extremely)増えたにも関わらずサイズが-9よりも大きくなっています。また、-6と-9は同じサイズになっています。-9で行う計算が-6と差ができる程度には利かなかったようです。
展開はbzip2が最も遅く、xzは-9e以外は0.4秒弱に収まっていますが、gzipの0.2秒未満と比べると少し遅いです。
今回のxz -0はgzip -9よりも速くはありませんが、bzip2を圧縮速度、サイズ、展開速度で上回りました。
まとめ
今回実験に使ったハードウェアは少し古いノートパソコンなので、典型的なmikutterユーザ/開発者の環境に比べると計算速度、IO速度共に多少遅いかもしれません。
また、LZMA系のアルゴリズムはバイナリファイルでも圧縮率がそれなりに良いのですが、mikutterのソースはほぼテキストファイルなのであまり差が出ませんでした。
今回実験に使ったGNU tar 1.22は2009年にリリースされた、xz圧縮/展開をパイプせずに行えるバージョンです。また、最近のUbuntu、最近のMac OS Xにはデフォルトでxzがインストールされているようです。(参考:Re: xz 圧縮について (M+ OUTLINE FONTS 640) - M+ FONTS - OSDN、Re: xz 圧縮について (M+ OUTLINE FONTS 643) - M+ FONTS - OSDN)
xz圧縮はここ数年でパッケージ管理ソフトウェアのアーカイブでも利用されるようになりました。まだ使ったことのない方は是非試してください。
よりClojureらしく線形合同法を使った乱数列を計算する
Clojure入門がてらに「初心者用課題」を解いてみる - 蟲!虫!蟲! - #!/usr/bin/bugrammerの第四問を解いて、解説を加えたものです。
前回(よりClojureらしく閏年を探す - OGINO Masanori@はてな)を踏まえた説明なので、前回を読んでいない方はそちらを先に読んでください。
漸化式を使ったシーケンス
はてなの線形合同法のページは、線形合同法を次の漸化式で表現しています。*1
この式のs, a, b, mは最初に値を決めたら変化しません。今回はlcgの引数として受け取ることにします。ちなみにsは乱数の種(seed)です。
今回の問題では、x1, x2, x3, ...を要素として持つシーケンスがあればいいので、漸化式を変形する必要はありません。
繰り返し、繰り返し
前の値を二つ目の式に繰り返し与えながらシーケンスの要素にすればいいのですが、Clojureにはそのための関数があります。iterateです。
iterateは関数と初期値を受け取り、x, (f x), (f (f x)), ...を要素に持つ無限シーケンスを返す関数です。
;; REPLに入力する前に*print-length*にあまり大きくない整数をset!すること! (iterate inc 0) ;=> (0 1 2 3 ...)
iterateに渡す関数は、今回も第一問のように#()で作ってもよかったのですが、せっかくなのでfnを使ってxと書けるようにしました。fnは関数を返します。
「fnは関数を返す関数」?
前々から気付いていたかもしれませんが、私はよく「○○は△△を受け取り、××を返す関数です」という形で関数を紹介してきました。
しかし、fnの説明は「返します」で終わっています。それはfnが関数ではないからです。fnは特殊フォーム(special form)で、特殊フォームは関数ではありません。
特殊フォームとは何なのか、特殊ではないフォームはどこにあるのか、そもそもフォームとは何を指すのか。それらについてはいずれ別の文章の中で書く予定です。
remか、modか
Clojureはいわゆる「余り」を返す関数を2つ持っています。今まで使ってきたremと、もう一つはmodです。
今回はx, a, b, mのどれかに負の数が入らなければremを使ってもmodを使っても値は同じですが、式にmodとあるのでmodを使います。*2
remとmodの返り値が異なる例を示します。
;; 17 = -5 * (-3) + 2 (rem 17 -5) ;=> 2 ;; 17 = -5 * (-4) + (-3) (mod 17 -5) ;=> -3
これで、乱数のシーケンスを手に入れました。あとはdropでx0を捨てて*3、元のコードに合わせるために逆数を取ってから浮動小数点数に変換します。
/は引数が一つの場合に数値を一つ受け取り、その逆数を返す関数です。floatは数値を一つ受け取り、(単精度二進)浮動小数点数に変換して返す関数です。
(/ 0.5) ;=> 2.0 (/ 2) ;=> 1/2 (float 1/2) ;=> 0.5
見ての通り、Clojureは有理数型と有理数リテラルを持ちます。
有理数型のない言語に慣れている方は落ち着かない気分になるかもしれませんが、必要になるまで浮動小数点数に変換しないようにすると丸め誤差の影響を軽減できます。
平均を求める
元のコードだとrandom-meanと書かれていますが、このmeanは平均を意味している*4と思うのでlcg-averageという名前にしました。
また、元のコードではシーケンスと平均を出力していますが、好みで*5平均を返すようにしています。シーケンスも必要ならリストか何かに一緒に入れて返せば実現できます*6。
このシリーズで初めて登場するletとapplyを以下で解説します。
名前を付ける
letは値に名前を付けることができます。*7値に名前を付ける(または名前と値を結び付ける)ことを「代入」と呼ぶプログラミング言語もありますが、Clojureは「束縛」と呼ぶプログラミング言語の一つです。
(let [x 1] ,,,)
この例で言うと、「letはxを1に束縛する*8」または「xは(letによって)1に束縛される」と言います。
束縛したい名前と対応する値は[名前 値 名前 値 ...]と交互に並べます。*9*10束縛された名前は、letの内側で参照できます。そして、letも式であり、内側の式の値がlet全体の値になります。
(let [x 2 y 3] (+ x y)) ;=> 5
letについて書くことはまだあるのですが、今回はlet以外にも説明すべきことがあるのでここで止めておきます。
適用する
applyは関数と「その関数の引数にしたいもの」を受け取り、その関数に「引数にしたいもの」を渡して呼び出し、その関数の返り値を返す関数です。
「引数にしたいもの」を持っていながら、その関数を直接呼び出せないことがあります。それは引数をリストなどの形で持っている時です。applyはそうした時に使います。
applyを「引数として渡したいリストを一つ受け取る関数」と勘違いする*11ことは珍しくありませんが、Clojureのapplyはリストなどの前に引数をそのまま書けます。
(apply + 1 2 3 '(4 5)) ;=> 15
だからといって、次のようなことをしても意味はありません。*12
(apply + 1 2 3 4 5 '()) ;=> 15
そして、説明が遅れましたが、+は3個以上の引数を受け取ることができます。
(+ 1 2 3 4 5) ;=> 15
これでlcg-averageの解説も十分だと思います。
復習問題
余裕があればよりClojureらしい素数列 - OGINO Masanori@はてなとよりClojureらしく閏年を探す - OGINO Masanori@はてなの(drop ,,, (range))を(iterate ,,, ,,,)の形に書き直してみましょう。
Clojureのコードも「やり方は一つじゃない」のです。
おわりに
今回は今までより文章の量が多いので、読むのがつらかったかもしれません。ごめんなさい。
次回は「数当てゲーム」を予定しています。今回説明するまでiterateを使わなかったように、これまでの三問で使わなかったものを使ったプログラムになります。
*1:意味を変えない範囲で変形しました。
*2:線形合同法で使う剰余の定義を確かめるのが「正しい」方法です。
*3:ここはrestでも構いませんが、今までの解説から「1個捨てる」ことが分かりやすいようにdropを使いました。restを使った方が読みやすい場合もあります。
*4:違ったら指摘してください。
*5:「xが大きい場合はシーケンスの出力に時間がかかる」という理由もありますが、「出力するよりも、単に返して呼び出す側が出力するかどうか選択する方が好き」という理由が大きいです。
*6:多値のある言語に慣れた方へ:Clojureに多値そのものはありません。
*7:letも特殊フォームです。
*8:「代入」するプログラミング言語では「xに1を代入する」と呼ぶことに気をつけてください。「に」と「を」が入れ替わっています。
*9:Clojureには分配束縛という機能もありますが、ここでは解説しません。
*10:他のLispに慣れている方へ:Clojureではnilで初期化したい場合に(let [foo nil] ,,,)のようにnilを明示します。
*11:そういうapplyを実装した言語もあります。
*12:Clojureは変数と関数の名前空間が分かれていませんから、たとえ変数の値としての関数であっても普通に呼び出せます。
よりClojureらしく閏年を探す
Clojure入門がてらに「初心者用課題」を解いてみる - 蟲!虫!蟲! - #!/usr/bin/bugrammerの第二問を解いて、解説を加えたものです。
前回(よりClojureらしい素数列 - OGINO Masanori@はてな)を踏まえた説明なので、前回を読んでいない方はそちらを先に読んでください。
どこかで見た構造
前回を読んでいれば、素数列のコードと今回の閏年のコードの構造の類似点は容易に発見できると思います。
leap-year?はグレゴリウス暦(グレゴリオ暦)の閏年の定義そのものなので、解説する所は特にありません。
練習問題
- 400で割り切れる場合
- 400で割り切れないが、4でも100でも割り切れる場合
- 400でも100でも割り切れないが、4で割り切れる場合
- 400でも100でも4でも割り切れない場合
Clojureのコードを読み慣れていない方は、どの場合でも真偽値が返ってくるかどうか考えると読解の練習になるかもしれません。述語の存在を意識するのが要点です。
やっぱりどこかで見た構造
leap-yearsは前回のprime-numbersとほとんど同じですが、1582個もの要素を捨てています。
これは、グレゴリウス暦は1582年に制定されたらしいので、0*1から1581まで飛ばしただけです。飛ばさなくても今回のような練習問題では問題ありません。
1582個もの要素を計算して使わずに捨てるのはもったいないと思う方*2は、rangeのコードを表示して読んでみてください。1582から始まる無限シーケンスを作るヒントが見つかると思います。(見つからなかったら質問してください。)
(source range)
おわりに
同じような問題は、きっと同じようなコードで解けます。でも、違ったコードで解けるかもしれません。
第三問のBuzzFizz(FizzBuzzのFizzとBuzzの条件を逆転させたもの?)は、無限シーケンスを使うようにする以外に提示する意義のある方法が特にない*3と思うので、スキップします。
次回はよりClojureらしく線形合同法を使った乱数列を計算する - OGINO Masanori@はてなです。
あまり読まれないものと思っていましたが、問い合わせがあって少し驚きました。ありがとうございます。
よりClojureらしい素数列
私は一昨日から2011-09-24 - 蟲!虫!蟲! - #!/usr/bin/bugrammerの補足を書いていましたが、分量が多すぎるので後日分割することにしました。
そこで、代わりに2011-09-27 - 蟲!虫!蟲! - #!/usr/bin/bugrammerの問題を解いて解説をつけたものを先に公開します。
しかし、それも解説を含めると長いので、問題ごとに分割して記事を公開します。ツッコミ歓迎。
始める前に
解説してない部分はdocを使って調べて、疑問が残っていたら気軽に質問してください。
まずdocstringから始めよ
まず目につくのは名前と引数の間にある文字列ですが、これはdocstring(ドキュメンテーション文字列)と呼ばれます。Javadocとほぼ同じ役割を持ち、docなどで見られる説明はこれです。
docstringを書くのは(関数名と引数を繰り返すようなものを書かなければ)良い習慣です。もちろん、内容を変更したらdocstringも更新します。
素数?
Clojureでは、真偽値*1を返す(副作用のない)関数を述語と呼び、述語には?で終わる名前を付ける慣習*2があります。
ここに登場する述語はzero?、not-any?、prime?です。zero?は名前の通り、数値を受け取って0ならtrueを、そうでなければfalseを返す述語です。
内容を読み解かなくても、prime?が述語だということは想像できます。not-any?という(正体をまだ知らない)述語の返り値をそのまま返しているからです。
not-any?は述語とシーケンス*3を受け取り、シーケンスの要素一つ一つに対して述語を適用して、trueを返すものが一つもなければtrue、そうでなければfalseを返す述語です。
(not-any? zero? '(1 2 3)) ;=> true (not-any? zero? '(0 1 2)) ;=> false
ここで;=>と書いたのは、左側の式を評価*4すると右側の値が返ってくるという意味です。
つまり、
(not-any? zero? ,,,)
は,,,に来るシーケンスの要素に0がない場合にtrue、ある場合にfalseを返します。
そして,,,の部分はこうなっています。
(map #(rem x %) (range 2 (inc (/ x 2))))
これは、2以上(x/2)+1未満を満たす整数のシーケンスを作り、それぞれの要素でxを割った余りのシーケンスに変換しています。
x/2が現れるのは、最小の素数が2なので、どんな合成数x自分自身を除くx/2より大きい自然数で割り切れることはないからです。*5
#(rem x %)は(partial rem x)とも書けますが、ここでは深入りしません。関数に慣れた頃に調べると良いと思います。
まとめると、prime?は「xが2以上(x/2)+1未満のどの整数でも割り切れなければtrue、そうでなければfalseを返す」関数です。
しかし、docstringに書くべきことはアルゴリズムではなく意図や用途ですから、「xが素数であればtrue、そうでなければfalseを返す」と書いています。
素数とは
このコードの山場はprime?なので、後はそう複雑ではありません。
(filter prime? (drop 2 (range)))
rangeは引数がない場合に0, 1, 2, 3, ...を要素に持つ無限シーケンスを返す関数です。
無限シーケンスと言っても、無限のメモリを要求するわけではありません。rangeを始め、多くのシーケンス関数は必要になるまで計算しません*6。
(set! *print-length* 10) ;=> 10 (range) ;=> (0 1 2 3 4 5 6 7 8 9 ...)
(set! *print-length* 10)が重要な所で、これはREPLなどがシーケンスを表示しようとする際に先頭から何個表示するかを指定します。これがないと、無限シーケンスの全てを表示しようとして、スレッドやプロセスを止めるまで反応しません。
(drop 2 (range))
dropは整数とシーケンスを受け取り、先頭からn個捨てて残りを返す関数です。ここでは0と1を捨てます。
(filter prime? (drop 2 (range)))
filterは述語とシーケンスを受け取り、述語に適用してtrueを返す要素だけを持つシーケンスを返す関数です。
まとめると、prime-numbersは「2, 3, 4, ...の中の素数を抜き取った無限シーケンスを返す」関数ですが、要するに素数列を返す関数です。
欲しい時に、欲しい分だけ
元のコードでは整数を一つ取って「xより小さい」素数をシーケンスとして返すようになっていましたが、これは引数がありません。しかし、「xより小さい素数」が必要ならすぐ手に入れられます。試しに100未満の素数を手に入れます。
(take-while #(< % 100) (prime-numbers))
先頭から100個欲しい時もあるかもしれません。
(take 100 (prime-numbers))
100番目の素数が欲しい時もあるかもしれません。
(nth (prime-numbers) 99) ; Clojureでは0番目から数え始める点に注意!
prime-numbersは遅延シーケンスによって計算を必要になるまで延期しますが、同時に「何が必要なのか」という選択も必要になるまで延期できるようになりました。
今、いくつかの関数を解説せずに使いましたが、初めて見たものはdocで調べてください。例えば、こんな風に。
(doc take-while)
おわりに
このコードは効率より読みやすさを優先しました。速度を求める場合はより洗練されたアルゴリズムを使う必要があるでしょう。
より高速なバージョンの一つについて、第五問の解説の中で言及します。今回書かない理由は解説の流れの都合上そうした方が良いと判断したからです。ごめんなさい。
第三問については特に補足するような事もないので、第二、四、五問の添削もいずれ行いたいと思っています。*7
私より優れたClojurian*8はたくさんいるので、誰かが私のコードを更に添削するかもしれません。むしろ添削してください。お願いします。
追記:Gaucheで「よりClojureらしい素数列」
Gaucheの作者、川合史朗さんがこのページのコードをGaucheで書き直したものをhttp://blog.practical-scheme.net/shiro/20110927-primesで公開しています。
メタデータのこれまでとこれから
最初に謝らなければならないことがあります。ごめんなさい。私が間違っていました。
耳当てつきのvarはダイナミックスコープを持つと自動的にみなされない - OGINO Masanori@はてなで、:dynamicメタデータの値をtrueにするために、このように書きました。
(def ^:dynamic *fred*)
^:dynamicという書き方は1.3.0から追加されましたが、これを1.2のREPLに入力しても例外は発生しません。
(clojure-version) ;=> "1.2.1" (def ^:dynamic *fred*) ;=> #'user/*fred*
私はこれを見て「1.2でも動くんだな*1」と思い、そう話しました。
私が間違っていました。コンパイラに渡すと「Unable to resolve classname: :dynamic」という例外が発生します。
以下にその理由と対策を示します。
これまでのメタデータ
1.0では、^fooは(meta foo)に展開され、varにメタデータを付加する際には#^を使いました。
;; (meta #'str) ^#'str (defn #^{:tag String} long-hello [#^{:tag String} your-name] (str "hello, " your-name))
この:tagメタデータは型を指定するもので、コンパイラにリフレクションを使わないコードを生成させるために付加します。
この:tagメタデータには#^Klassというショートカットがあり、#^{:tag Klass}に展開されます。
(defn #^String short-hello [#^String your-name] (str "hello, " your-name))
しかし、1.1で^fooは非推奨となり、1.2で削除されました。用途がなくなった^は#^の新しい書き方として再定義され、1.2で#^は非推奨となりました。
(defn ^{:tag String} long-hello [^{:tag String} your-name] (str "hello, " your-name)) (defn ^String short-hello [^String your-name] (str "hello, " your-name))
これが今回のコンパイルエラーの原因です。:dynamicという名前のクラスを探して失敗していたのです。
これからのメタデータ
そして、1.3では:dynamic以外にもtrueかどうかをコンパイラが調べるメタデータが追加されたので、^:keywordが^{:keyword true}に展開されるようになりました。
最後に、耳当てつきのvarはダイナミックスコープを持つと自動的にみなされない - OGINO Masanori@はてなの最後のコードを1.2と1.3の両方で動かす方法を示します。
一つは、^{:dynamic true}と書くことです。1.2のコンパイラは:dynamicを考慮しませんが、害もありません。
(def ^{:dynamic true} *fred*)
1.3へ完全に移行してから^:dynamicに書き直してもいいですし、書き直さなくても特に問題はありません。
もう一つは、1.2のコンパイラが:tagメタデータを最初の一つだけ読み込む点を利用して、先に型を指定することです。
;; *fred*は文字列 (def ^String ^:dynamic *fred*)
しかし、少々トリッキーなので、私はあまり好みません。
おわりに
私の検証不足で人に誤りを伝えました。申し訳ありません。
耳当てつきのvarはダイナミックスコープを持つと自動的にみなされない
これはClojure 1.3.0のchanges.txtの1.1の日本語訳と解説です。
日本語訳
(def *fred*) => Warning: *fred* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *fred* or change the name. (警告:*fred*は動的と宣言されていないので、動的に再束縛することができませんが、名前がそうでないことをほのめかしています。 ^:dynamic *fred*と明示するか、名前を変えてください。)
解説
1.2系のコードを移行する時によく見かける警告です。
Lispから受け継いだ慣習に「ダイナミックスコープを持つ変数は名前の前後をアスタリスクで囲んで宣言する*1」というものがあります。
1.2.xまではすべてのvarがダイナミックスコープを持つ変数だったので、(恐ろしいことに)こんなこともできました。
(defn answer [] (* 2 3 7)) (answer) ;=> 42 ;; *が動的に+へと再束縛される (binding [* +] (answer)) ;=> 12 (answer) ;=> 42
1.3から:dynamicメタデータの値がtrueな値のみがダイナミックスコープを持つので、ダイナミックスコープをほのめかしている名前のvarがダイナミックスコープを持たない場合に警告を表示するようになりました。
ダイナミックスコープを持つ変数として使っている場合は:dynamicメタデータを付け加え、慣習を知らずに強調のためにアスタリスクで囲んでいる場合は紛らわしいのでアスタリスクを外しましょう。
(def ^:dynamic *fred*) ;; または (def fred)
:dynamicメタデータを付け加える際に使った^:dynamicという書き方については、後で別に解説します。