ハツカネズミの恋

Lisp のもろもろ,おぼえがき

Anathema の Weather Systems の歌詞を淡々と解説していく.

Untouchable, Part 1

and I feel like I knew you before

and 接続詞から歌詞が始まる.明示的に語られないここまでの物語の奥行きを感じさせる. know の用法として,know of, know about などがあるけど, 単独の know が直接的に目的語を取るケースはニュアンスが強くて, 目的語についてよく知っている印象を受ける.

and I guess that you can hear me through this song

ここの and は前節の and と同形の接続詞. 前節の文もこの文も,直前で示された文に従う.が,その文は明示されない. hear は目的語に me という人称を引き受けて,声を聴く という意味. 強い know と 曖昧なguess の対比が綺麗.

and my love will never die

and を繰り返すことで,and が従っている文の内容に文脈をつけてゆく. and Iand my が韻として対応. die はよくある比喩で,end くらいに解釈すればいい.

and my feelings will always shine

my feelings が前節の my love を包含するかは解釈次第. dieshine が文末の韻として対応. my feelings will shineshine という語の一般性をどの方向に解釈するかに依存する.含みの多い文. もっとも一般的な shine に対して, - glow は熱した鉄や蛍光塗料のようなぼうっとした光. - sparkle は光源が小さく発光時間も短い. - gleam は青白い光のイメージ.表情の明るさにも用いる.

and my love will never die and my feelings will always shine

and I know that you just want me to belong to someone

再びknowguess の対比. that 以下の内容は前回とは異なる. want me to belong to someone は文末の else が落ちていると解釈するのが自然と思う. 他の誰かのものになる,という日本語同様,恋愛をはじめ,人間関係全般に適用出来る表現.

and I guess that now I`ll just be moving on to someone

I know として you の状態については確信を持ちつつ, 自身の状態については I guess と現在進行形を用いてぼんやりと表現.

and my love will never die and my feelings will always shine and my love will never die and my feelings will always shine

I`ve never betrayed your trust

現在完了と never が効果的. かつて一度もあなたからの信頼(trust)を裏切ったことはない.

I`ve never betrayed your faith

ここで faithtrust の同義語として用いられている印象. faithtrust よりも一般に 宗教的信仰 のニュアンスが強い.

I`ll never forsake your heart

I'll は I've の時制と対応関係がある. forsake の原義は信仰を止めること.人に対しては"見限る"くらいの訳語となる.faith と betray の両方の意味を含んだこの語で前節を受けている. heart は 前々節 trust の語末の t を受ける.

I`ll never forget your face

前節と同じく I'll never で I've never を受ける. forget は forsake と頭の子音で韻を取る. face は 前々節 faith と語全体の音が対応.

theres a feeling that I cant describe

there`s a reason that I cannot hide

reason は feeling の語末の韻を取る

cause Ive never seen a light that`s so bright

light の韻を bright が受けている.

the light that shines behind your eyes

shines の韻を eyes が受けている.

Ive never betrayed your trust Ive never betrayed your faith Ill never forsake your heart Ill never forget your face

theres feeling that I cant describe a reason that I cannot hide Ive never seen a light thats so bright the light that shines behind your eyes

I can see this life and what you mean to me and when I dreamed, I dreamed of you then I wake, tell me what I do

I had to let you go to the setting sun I had to let you go to find your way back home I had to let you go to the setting sun I had to let you go to find your way back home

Clojure ことはじめ② - 関数 -

関数

Lisp

Lisp という単語が,これまで特になんの説明もなく,幾度か登場した.Lisp とは何か,という問いに率直に答えるのは難しい.Lisp に限らず,x とは何か,という問いに率直に答えるのはいつだって難しい.だからここでは,以降の内容を読み進める上で最低限知っておいた方がいいと判断した部分について,Lispを簡単に紹介しておこうと思う. まず,大方お察しのことと思うが,Lisp とはプログラミング言語だ.ただし,Lisp という名前のプログラミング言語が唯一の実体として存在するわけではない.強いて言えば 純Lisp と呼ばれるものがそれにあたるが,実用性に乏しく,Lisp の代表とするには心もとない.一般に言われる Lisp とはいわば,プログラミング言語の,ひとつのパラダイムだ.Lisp という名前は List Processer をもじったもので,もともとは単に表記法として発明された.それは,コンピュータのプログラムを数学的な正確さを失わないように記述するための表記法だった.Clojure のコードでも散々目にして,そして,とても大切な役割を担ってきた,カッコを駆使した表記法.特に’S式’と呼ばれるあの表記法こそが,当初は Lisp の本体だった. Lisp はその後すぐにプログラミング言語として実装され,冒頭で紹介した Common Lisp や,Emacs で大活躍するEmacs Lisp,よりコンパクトな仕様を持ち関数型の傾向が強い Scheme など,数々の子供たちを生んだ.Lisp の主要なアイデアを継承したこれらの派生言語を,Lisp 方言と呼ぶ.もちろん,ClojureLisp 方言だ.ではそのLisp 方言に共通する主要なアイデアとはどんなものだろう.様々な意見があるだろうが,多くの Lisper が挙げるのは,およそ以下のようなものだろう:

  • 統一的な文法の基礎としてのS式
  • マクロに代表される強力なメタプログラミングシステム
  • コードとデータを区別しない
  • リスト処理が豊富である
  • 関数型(的)プログラミングスタイル

Lisp の歴史は古く,当時MITで教鞭を執るジョン・マッカーシーの手によって生み出されたのは1958年,現役で動いているプログラミング言語としては Fortran と並んで最長老の一角だ.しかし,Lisp は流行とは無縁な言語だった.その言語仕様の簡潔さや表現力,アイデアのるつぼのような斬新さは一部の人々 (オタク) を惹きつけたが,ついぞ広く大衆に浸透する機会はなかった.その理由はいくつかあるが,カッコの多さで目がチカチカしてしまう,というのも一因かもしれない.Lisp はアカデミックな対象と見なされ,’黒板上で最も実装されている言語’ なんていう皮肉まであったほどだ.逆に言えば,仕組みや理論を奉じるオタク達からの支持は厚く,Lisp を素材にした素晴らしいコンピュータサイエンスの教科書も数多く生まれた.そして,こんな風に今も我々プログラマの目の前で元気に動いている.それも,登場した時の美しい姿を保ったままで.

関数

Lisp に心惹かれるプログラマの多くは,そのシンプルさに魅入られている.たぶん.というのは,一見して複雑な処理をしているプログラムでも,そのビルディングブロックとなる関数自体は非常にシンプルだからだ.シンプルと言えば,Clojure の作者である Rich Hickey による 'Simple Made Easy' という有名な講演がある.Clojure の設計思想に触れることのできる素晴らしい内容なのでぜひ一見をおすすめする.シンプルさを矜持とする彼が Lisp に目をつけたのは決して偶然ではないと確信するだろう.

関数呼び出し

まず,あらゆる Clojure のオペレーションは統一されたシンタックスを持っていることを思い出そう.復習だ.カッコを開いて,直後にオペレータ,続いてオペランドを並べ,カッコを閉じる.このルールは関数呼び出しの時も変わらない.なぜって,Clojure における関数呼び出しというのは,単に関数,もしくは関数式をオペレータとするオペレーションに過ぎないからだ.引数も同様に,オペレータが関数の時のオペランドを指す言葉だ. 関数式というのは,文字通り関数を返す式のこと.例えば,以下のオペレーションは関数式だ.

(or * + -)

ちょっと見た目が変に見えるけど,それは単に見慣れないというだけ.正しく評価されるし,値も返す.返ってくる値は掛け算の文字列表現だ.

(or * + -)
;=> #object[clojure.core$_STAR_ 881975570 "clojure.core$_STAR_@3491e112"]

or はオペレーション内で最初に真に評価された値を返すんだった.だから,こんなことができる.

((or * + -) 3 4)
;=> 12

この例で,(or * + -) 全体の評価結果は,最初の真値である * だ.すなわち,(* 3 4) という式が生成され,これを評価して12という結果を得ている.こうしたネスト構造は,オペレーションのシンタックスに従う限りいくらでも深くすることが可能だ.

((and (= "yes" "yes") (or * + - nil "no")) 5 6)
;=> 30

複雑化しているものの,and オペレーションの全体はあくまでも一つ外側のカッコにおけるオペレータの位置に収まっている.このことは,直後に数値リテラルオペランドが続いていることからも見て取ることができる. では,今度はあえて,オペレーションのシンタックスから外れた書き方をしてみよう.どうなるか.

("This" :shall :not :pass)
;=> Execution error (ClassCastException) 

この手のエラーはしょっちゅう目にすることになる.これは関数のつもりでオペレータの位置に置いたものが,実際は関数でなかった時に投げられる例外だ.

第一級関数

Clojure における関数の扱いは非常に柔軟だ.上で見た関数式もその一例だが,それだけには止まらない.Clojure の関数は,引数として関数を受け取り,さらに,関数を返すことができる.こうした関数のことを一般に高階関数という.こうした高階関数の扱いが可能なプログラミング言語は,関数を第一級オブジェクトとしてサポートしている,とよく言われる.第一級オブジェクトというのは要するに,言語の基本的な操作を無制限に適用できるオブジェクトのことだ.Clojure では例えば,数値リテラル,ヴェクタなどが第一級オブジェクトで,つまり関数はこれらの値と同じように処理される.ちなみに,第一級オブジェクトとオブジェクト指向のオブジェクト,これらは同じ言葉でも意味が異なるので混同しないように注意しよう. ここでは,map という関数を例にとって,Clojure が関数をどんなふうに扱っているのか観察してみよう.その前に,map と協働してもらう inc 関数を最初に紹介する.

(inc 1)
;=> 2

(inc 2.5)
;=> 3.5

簡単.引数の数値に1を足した値を返すだけだ.さて,これを map と組み合わせる.

(map inc [0 1 2 3 4])
;=> (1 2 3 4 5)

お分かりだろうか.ヴェクタ内の各要素に inc が適用され,その結果が返されて……おや,何かおかしい.map の二つめの引数はヴェクタだったはずなのに,返された値はええと……リスト? と,訝しんでいる人がいるかもしれない.でも,大丈夫,結果はこれで正しい.なぜ map が素直にヴェクタを返さないのか,その理由はあとでちゃんと説明する.きっと,そこでClojure の柔軟さに感じ入るはずなので,今はいったん’そういうもの’,として受け入れて欲しい. map はヴェクタやリストといったコレクションの変更を一般化した関数で,あらゆる関数をあらゆるコレクションに適用することができる.つまりmap を使えば,コレクションの各要素に,任意の関数をいっぺんに適用して,新しいコレクションを生成することができるんだ.戦隊モノのヒーローがヴィランを前にして一斉に変身する場面なんかを思い浮かべてみて欲しい.あれも map で表現できる.

関数定義

さて,完全な思いつきだがせっかくなので,関数を定義する上での例として戦隊ヒーローを取り上げてみたい.ちょっとこじ付けっぽい部分が出てくるかもしれないけど,そこにはどうか目を瞑って欲しい. ではまず,Clojure で任意の関数を定義するための基本的な方法から確認しよう.

(defn name
 "docstring (optional)"
  [params]
  body)

defn は関数を定義するためのマクロだ.マクロが何かは,まだ気にしなくていい.name の部分は関数の名前としてシンボルが入る."docstring (optional)" は関数に関する説明を文字列で記述する場所で,あってもなくてもいい.params はパラメータ,その関数が受け取る値の名前だ.パラメータはいくつあってもいい.body は文字通り,関数の本体を記述する.具体的なオペレーションをここに書く. 何はともあれ,まずはやってみないと始まらない.早速ひとつ,関数を定義してみよう.

(defn transform
  "transform a human into a brave soldier"
  [member]
  (str member "-Ranger"))

 (transform "Rich")
;=> "Rich-Ranger"

我々はこうしてリッチ・ヒッキーをリッチレンジャーへと変身させることに成功した.この関数は,ご覧の通り,あるメンバーを一人文字列として受け取って,その文字列に "-Ranger" という新たな文字列を合成した値を返す.では,リッチ・ヒッキーにはここでいったんご退場いただいて,悪と戦う勇敢な隊員たちを新たに定義しよう.定義にはもちろんdef を使う.

(def fighters ["Red" "Blue" "Green" "Yellow" "Pink"])

古き良きゴレンジャースタイルだ. さて,今定義したヴェクタ fighterstransformmap してみる.

(map transform fighters)
;=>("Red-Ranger" "Blue-Ranger" "Green-Ranger" "Yellow-Ranger" "Pink-Ranger")

よし!変身完了!五人揃ってゴレンジャーだ!

(def go-ranger (map transform fighters))

(println go-ranger)
;=> (Red-Ranger Blue-Ranger Green-Ranger Yellow-Ranger Pink-Ranger)

アリティ

Clojure の関数は0個以上の引数によって定義される.この,関数に渡される引数の数をアリティ (arity) と言う.Clojure ではアリティによって,関数の挙動を変えるような書き方ができる.例えばこんな感じ.

(defn zero-arity
  []
  "I take no params!")

(defn one-arity
  [x]
  (str "I take only one params :" x))

(defn two-arity
  [x y]
  (str x " and " y " are our params"))

さらに,Clojure の関数はアリティをオーバーロードすることもできる.これはつまり,引数の数(アリティ)によって実際に実行する関数の本体を場合分けできるということだ.具体例から説明すればきっとピンとくるだろう.

(defn transforming
  ([name type]
   (str "The brave fighter " name " has been transformed into " type "!!")) ;; body-1
  ([name]
   (transforming name "Rich Hickey"))) ;; body-2

(transforming "Hayata" "Ultra-Man")
;=> "The brave fighter Hayata has been transformed into Ultra-Man!!"

(transforming "Hayata")
;=> "The brave fighter Hayata has been transformed into Rich Hickey!!"

ここで定義した関数 transformimg はアリティによって呼び出す関数の本体を変えている.2アリティの場合,つまり,nametype という2つの引数が渡された場合,transformimg は body-1 を評価する.1アリティの場合は body-2 を評価する. Clojure では,任意個のパラメータを引数にとるような関数も自然に書くことができる.’これ以降の引数はリストとして処理してね’ という印として,& を用いる.さっきゴレンジャーのくだりで書いた transform をすこし融通して書き直そう.

(defn transform-all
  [& fighters]
  (map transform fighters)

こうすれば,fighter に幾つでも要素を入れられる.そして,それらの要素はリストとして処理され,まとめて fighter という名前がつけられるので,一気に処理することができる.もちろん,通常のパラメータとリスト化パラメータを混ぜて使っても問題ない.

test

実は,Clojure にはパラメータを定義するための,もっと冴えた方法がある.それは,分配束縛 (distracting) と呼ばれる方法だ.

分配束

ビジネスロジックってなんやねんという話

tl; dr

ドメインロジック(domain logic) と言ってくれ.

ビジネスロジック

Rails について調べていると目にする機会の多いビジネスロジックという単語. めちゃくちゃ雰囲気はあるけど,雰囲気がありすぎて意味が掴めない語の筆頭.

e.g.

”Model にはビジネスロジックを記述し……”

ビジネスロジックをコントローラーに書いてしまうと……”

Wikipedia(jp) によれば:

ビジネスロジック(英: business logic)は、データベース上のデータに対する処理手順といったようなものを指す、ソフトウェア工学的な用語である。「アルゴリズム」という語が説明に使われていることがあるが、アルゴリズムは数学的・論理的に明確な概念であり間違った説明の仕方である。どんなプログラミング言語の仕様を見ても、APIを見てもビジネスロジックという用語は出てこないし、学術論文にも現われない。基本的には、エンタープライズ系(業務支援系)ソフトウェアを開発する企業が内部的に、もしくは顧客への販売促進のために用いる用語である。

お前は何を言っているんだ.

調べた結果余計に混乱するという悲しい結末.これはもう,この言葉が使われる文脈からお気持ちをお察しするしかない.

文脈とキーワード

ビジネスロジックという語が登場する場面では,それに付随する特有の文脈と共起表現がある.特に目立つものとして,例えば:

  • MVC
  • Model
  • Database

などがある.

それぞれ Wikipedia(en) で定義を確認すると:

MVC:

Model–View–Controller (usually known as MVC) is an architectural pattern commonly used for developing user interfaces that divides an application into three interconnected parts. This is done to separate internal representations of information from the ways information is presented to and accepted from the user.

Model:

The central component of the pattern. It is the application's dynamic data structure, independent of the user interface. It directly manages the data, logic and rules of the application.

Database:

A database is an organized collection of data, generally stored and accessed electronically from a computer system. Where databases are more complex they are often developed using formal design and modeling techniques.

やはり特にビジネスロジック(or buisiness logic) という語は登場しない(Model の説明に "logic" は出てくる).

要するに:

  • MVCデザインパターンの一種である.
  • MVC というデザインパターンのキモとなる Model は,アプリケーションのデータやロジック,ルールを取り扱う部分である.
  • データベースとは電子的に管理されたデータの集合である.

ということらしい.

まあ,なんとなく知ってた.知ってたけど,結局ビジネスロジックってなんなんだ.

と思っていたら,Wikipedia(en) にまんま Business logic - Wikipedia という記事を発見:

In computer software, business logic or domain logic is the part of the program that encodes the real-world business rules that determine how data can be created, stored, and changed.

なるほど.

ビジネスロジックとは,"the real-world business rules" がどんな風にデータを加工するか (how data can be created, stored, and changed) をソフトウェア的に規定したものである,と.これはほぼ上記 Model の定義と一致する.

ビジネスロジックという語の解釈

この辺を踏まえてざっくりと読み換えると,ビジネスロジックというのは:

ソフトウェアに落とし込まれた(実際の)ビジネスのルールが,どんな風にデータを加工するかを規定したもの.MVC デザインパターンでは主に Model に記述される.

ものと解釈できそう.

ところで,”ソフトウェア的にどんな風にデータを加工するかを規定したもの”って,それはいわゆる”メソッド”なのでは. Rails を例に考えると,/app/models/ 配下に記述された DB データの加工メソッドたちは,それぞれがビジネスロジックと言えるのではなかろうか.

ビジネス?

え,それ,別にビジネス関係なくない

ビジネスではない "the real-world matters" を MVC に乗せてソフトウェア的に構成したい時にも,Model にDBデータの加工メソッドが書かれる,という指針は変わらないはずだ.英語的に business を "the things to deal with" みたいに解釈したとしても,"business logic" という表現には違和感がすごい.

とにかくこうやって,ドメインとケースの抽象度が逆転したような名前を付けるのは,脳内の包含関係がややこしくなってめちゃくちゃ混乱する.やめてほしい.

そこで,もう一度 Wikipedia(en) の Business logic の定義に立ち戻りたい.

In computer software, business logic or domain logic is the part of ...

domain logic って表現,めちゃくちゃ気に入りました.こっちの方が,本質を誤解の余地なく適切に表現していると思う.ソフトウェアが "the real-world business rules" になった時は,この domain が business になるだけ.直感的にもこっちの方がずっと自然だと思う.

まとめ

今後ビジネスロジックという単語を見たら,脳内でドメインロジックに変換して読みます.

my.cnf が見当たらない

sql_mode=only_full_group_by: 問題

とあるデータをバッチ処理にかけていた時のこと.突然のエラー.

Mysql2::Error: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'hoge.name' which is not functionally dependent on columns in GROUP BY clause; 
this is incompatible with sql_mode=only_full_group_by:

つまり:

hoge.name’ というカラムが GROUP BY 句に入ってないのに SELECT しようとしてるよ. これは sql_mode=only_full_group_by の設定に反するから,集計関数 (aggregated function) と一緒に指定してね. と言われている.

なるほど.じゃあその incompatibility とやらを葬ってやる. どうせそれっぽい名前の設定ファイルがあるんだろう?

dev.mysql.com

ということで上記 MySQL の docs を見たら,my.cnf なるファイルを発見.これだわ.

そしてそこにはこんなテーブルが.

f:id:ksysk:20190613105003p:plain
*nix における設定ファイルの一覧

罠かよ.どれに書いてあるんだその sql_mode=only_full_group_by ってのは.

ありったけの my.cnf を

困ったら help だ.ということで mysql --help したら長すぎて読む気が失せた.

そこで,結果に grep をパイプして my.cnf を探す.

mysql --help | grep my.cnf
order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/mysql/my.cnf /etc/my.cnf ~/.my.cnf

だからどれだよ.

とはいえ大した量でもないので,片っ端から ll して探す.

ll my.cnf
ls: my.cnf: No such file or directory
ll /etc/my.cnf
ls: /etc/my.cnf: No such file or directory
ll /etc/mysql/my.cnf
ls: /etc/mysql/my.cnf: No such file or directory
ll /usr/local/etc/my.cnf
-rw-r--r--  1 ks_y_sk  admin   113B May 29 14:14 /usr/local/etc/my.cnf

発見.早速開いてみるものの,特にそれらしき記述がない.

# Default Homebrew MySQL server config
[mysqld]
# Only allow connections from localhost
bind-address = 127.0.0.1

sql-mode を確認

こうなったら直接 MySQL 側から攻めよう.ということで:

mysql -uroot -e "SHOW VARIABLES LIKE 'sql_mode';"

ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 

やっと見つけたぞ許さん.こうしてやる.

SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));

ちゃんと確認しておこう.

mysql -uroot -e "SHOW VARIABLES LIKE 'sql_mode';"

STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 

その後 MySQL を再起動したら,無事に期待通りの処理ができた.嬉しい.

まとめと課題

  • sql_mode の定数について
    • 定数の種類と役割を理解する
    • MySQL のバージョンごとのデフォルト値を把握する
  • my.cnf について
    • MySQL のバージョンごとの記述内容を知る
    • 設定ファイル間の優先順位を知る

frozen-string-literal: true についてのメモ

脳死で Rubocop を導入すると,あらゆる .rb ファイルの冒頭に自動で挿入される意味不明な文字列として名高い #frozen-string-literal: true .意味不明な文字列は有意味な文字列に変換してしまえば基本的に問題ないはずなので,調べた.

tl; dr

文字通り,文字列型のリテラルを凍結 = イミュータブルにする(?)ためのおまじないだった.とはいえ,破壊的メソッドの適用を防ぐのが関の山で,再代入は防げない.弱い.

frozen-string-literal

freeze メソッドで加工された文字列リテラルのこと:

"hoge".freeze 

ではなぜ freeze なるメソッドが必要なのか.

ref.xaio.jp

freezeメソッドは、オブジェクトを凍結、つまり変更不可にします。

freeze は Object クラスに定義されている.Object クラスはすべてのクラスの親クラスなので,Ruby ではすべてのオブジェクトに freeze が使える.裏を返すと,Ruby ではすべてのオブジェクトがデフォルトでは変更可能なのか……?

そう思い立って,変更不可能な値の代表である定数について少し調べた.

Ruby における定数

定数の一般的な定義は以下:

プログラミングにおいて定数(「ていすう」または「じょうすう」、英: constant)とは、変数同様プログラムのソースコードにおいて、扱われるデータを一定期間記憶し必要なときに利用できるようにするために、データに固有の名前を与えたものである。 ただし変数とは異なり、一度初期化するとその内容(値)を変更することはできない。よって、内容が変化しないことが保証される名前が必要なときに使用される。

なるほど.

一方,Ruby の定数:

アルファベット大文字 ([A-Z]) で始まる識別子は定数です。 他にも、ソースエンコーディングUnicode の時は Unicode の大文字またはタイトルケース文字から始まる識別子も定数です。 Unicode 以外の時は小文字に変換できる文字から始まる識別子が定数です。 定数の定義 (と初期化) は代入によって行われますが、メソッドの中では定義できません。一度定義された定数に再び代入を行おうとすると警告メッセージが出ます。定義されていない定数にアクセス すると例外 NameError が発生します。

要するに,変更しようとすると怒られは発生するけど,結局値は変わる.つまりミュータブル.ミュータブルな定数という形容矛盾.羊頭狗肉

CITY = "SHIBUYA"

# 破壊的メソッドのレシーバにすると……
CITY.downcase!
CITY #=> "shibuya"
# 変わる

# 再代入
CITY = "SHINJUKU"
warning: already initialized constant CITY
warning: previous definition of CITY was here
# 怒られる
CITY #=> "SHINJUKU"
# しかし変わる

frozen-string-literal を使ってみる.

#frozen-string-literal: true

CITY.downcase!
CITY #=> RuntimeError: can't modify frozen String
    from (irb):3:in 'downcase!'
# 破壊的操作はランタイムエラーとして防げる

# 一方再代入は……
CITY = "SHINJUKU"
warning: already initialized constant CITY
warning: previous definition of CITY was here
# 怒られる

CITY #=> "SHINJUKU"
# しかし変わる

まとめ

Ruby ではクラスもモジュールも定数らしい.生成されたクラスオブジェクトが指定したクラス名の定数に代入されていると考えれば,定数がミュータブルなのも仕方ないのかも.もっと掘ったらもっと面白いことが分かりそう.近々掘る.

Clojure ことはじめ① - 導入 / データ構造 -

Clojureについて

ClojureJVM をプラットフォームとして動作するLispだ.Clojure というイカした名前にはいくつかの由来がある.ひとつめは,同音異義語である 'closure'.日本語でいうと閉包.この言葉,数学では位相空間論で登場するが,プログラミングの文脈では ’自由変数を関数内に閉じ込める役割を持った無名関数’のことで,高階関数の引数としておなじみ.まあ,今のところはそんなに気にしなくていい.ふたつめは,Common LispScheme と並んで広く浸透している Lisp のひとつ.Lisp には名前空間の数に対応して,Lisp-1, Lisp-2 の二種類があり,SchemeLisp-2 ,Common LispLisp-1,ClojureLisp-1 だ.名前空間とは何か,そもそもLisp って何か,という話はひとまず置いておこう.Clojure という名前について,Common Lisp からは 'C' と 'L' を拝借している.みっつめは,Java.お察しの通り,これは Clojure の 'J' にあたる.先述の通り,ClojureJVM (Java Virtual Machine) を言語プラットフォームとして採用していて,この点で他の Lisp とは異質だ.というのは,ほとんどの Lisp はそれぞれ独自のプラットフォームを持っている.Clojure を '実用的な' Lisp と評する向きはおおむねこの,JVM 上で動作する,という点に着目してのことだろうと思う.ClojureJava の蜜月はかなりのもので,詳しくは後述するが,Java を自在に操作することは,必須とは言わないまでも,目下の問題に対する Clojure によるアプローチをより豊かなものにするだろう.ひとつめの 'closure' はダジャレ的な意味合いが強いものの,(Common) LispJavaClojure という言語の設計や思想に直接的な影響を与えている.ではその設計や思想とはなんぞや,という話になるが,僕自身そこまで深く理解している自信がないので,おいおいまとめようと思う.また,REPL駆動開発や,Leiningen によるビルド方法など,具体的な開発手法や開発環境に関しては公式のドキュメントを鋭意翻訳中なので,そちらに譲る.

シンタックス

Clojureシンタックスはとてもシンプルだ.そして,Clojure のシンプルさは Lisp に由来する.統一的な構造,両手で足りるほどに厳選された特殊オペレータ,そしてちょっと目立ちがちな,親愛なるカッコたち.他の言語に触った経験があるひとならみんな,このカッコの量に最初はちょっと面食らうかもしれない.でも大丈夫,すぐ慣れる.カッコの管理に必要なコストはきっと思っているよりずっと少ないし,エディタなりIDEなりを導入すれば,対応関係をよしなに処理してくれる.何より,カッコは便利だ.カッコがなぜ,どんなふうに便利なのかは,これから学んでいく中で分かってくると思う.だから今はとりあえず,カッコは友達だってことを忘れないで欲しい.

フォーム

フォームというのは,ざっくりと式のことだと思えばいい.実際,色々なドキュメントで,フォームと式は同じものとして扱われている. もっとも,フォーム(ないし式)をそれ単体で記述する機会は稀で,たいていの場合,特定のオペレーションの中で用いることになる.以下,稀な機会としていくつかのフォームを単体で書き並べてみた.

2 ;;数値
"serial experiments lain" ;;文字列
["a" "vector" "of" "serial" "experiments" "lain"] ;;ヴェクタ

なんとも落ち着きが悪い感じがする.もしそう感じないのであれば,これから登場する他のサンプルコードをいくつか見てから,改めて上の例を見てみるといいかもしれない. オペレーションについては統一的な書式があるので,まずそれを紹介する.

(operator operand1 operand2 ... operandn)

まず '(' ,その直後にオペレータ,それ以降はオペランドで,最後に ')' で閉じる.ひとつのオペレーションにつき,オペレータがひとつなのに対して,オペランドはいくつあってもいい.とにかくこの,カッコで閉じられたひとかたまりが Clojure のオペレーションだ.Lisp の例にもれず,Clojureオペランドの区切り記号として原則的に空白を使う.

(+ 1 2)
;=> 3

(* 2 3 4 5 6)
;=> 720

(str "close the world, " "open the next")
;=> "close the world, open the next"

原則的にと書いたのは,視認性のため ' , ' を使う場合もあるからだ.これは後ほど紹介する.ここでは簡単に上の例を説明しよう.まあ,最初の二つに関しては,説明するまでもなく一目瞭然だろうけど.最初の例では + というオペレータが 12 というオペランドに適用されて,3 と評価されている.'適用(application)' とか '評価(evaluation)' とかいう言い回しは関数型プログラミングに特徴的なものだけど,さしあたりそこまで気にする必要はない.

制御フロー

ここでは,ifdowhen という3つの制御フローを紹介する.

if

まず,if フォームの基本的な形はこんな感じ.

(if test-form
  then-branch
  else-branch)

Clojureif オペレータを見つけると,直後のtest-form をまず評価する.test-form はブール値として truefalse に評価される.test-formtrue と評価された場合 Clojurethen-branch を評価し,false の場合は else-branch を評価する. この辺り,Clojure の真偽判定はちょっと特殊で,nilfalse だけが偽,その他の場合はすべて真となる.

(if nil "Stand Alone" "Complex")
;=> "Complex"

(if false "Stand Alone" "Complex")
;=> "Complex"

(if "Ghost in the Shell" "Stand Alone" "Complex")
;=> "Stand Alone"

(if 0 "Stand Alone" "Complex")
;=> "Stand Alone"

(if [] "Stand Alone" "Complex")
;=> "Stand Alone"

else-branch はオプショナルで,なくてもいい.ちなみに,test-formが偽で,かつ,else-branch がない場合はnilが返る.

(if nil "Stand Alone")
;=> nil

ご覧のとおり,Clojureifオペランドの配置順序が分岐先に関連付けられているので,then-branchelse-branch も,それぞれひとつのフォームしか持つことができない.これは評価結果の一意性という点ではすごく嬉しいことなのだけど,実際,ちょっと不便だ.そんな不便を解消するために,Clojuredo というオペレータを用意している.

do

if の中で do を使えば,複数のフォームをカッコで包んでそれぞれ実行することができる.例えばこんな感じ.

(if true
  (do (println "See you space cowboy...")
      "Rest in Peace")
  (do (println "See you cowgirl")
      "Someday, Somewhere!"))

;=> See you space cowboy...
;=> "Rest in Peace"

このように,do によって if の各分岐先で複数の処理を実行できる.上の例では,println によって 'See you space cowboy...' がREPL上に表示され,if というフォーム全体の評価結果として "Rest in Peace" という文字列が返されている.お察しのとおり,ifdo はしばしば使われる組み合わせだ.そこで,このふたつの機能をひとつにまとめたものが存在する.それが when だ.

when

whenelse-branch はない.英語における when という関係詞の用法から考えても,これは自然な振る舞いだ. whentest-formtrue に評価されると,以降のフォームを順次実行する.test-formfalse の場合は nil を返す.

(when true
  (println "See you space cowboy...")
  "Rest in Peace")

;=> See you space cowboy...
;=> "Rest in Peace"

もちろん then-branch がなく,else-branch だけのバージョンも存在し,その名もズバリ when-not

(when-not false
  (println "See you cowgirl")
  "Someday, Somewhere!")

;=> See you cowgirl
;=> "Someday, Somewhere!"

等価性

Clojure では,評価結果がブール値 (true/false) になる関数の末尾には '?' をつける習慣がある.例えば,オペランドnil かどうかを判定するのは nil? という関数だ.

(nil? 123)
;=> false

(nil? false)
;=> false

(nil? nil)
;=> true

オペランドnil 以外のすべての場合に true を返す some? という関数もある.これも,英語の some のニュアンスをよく反映している.' 何かしらの値が存在する?どう?' というわけだ.(余談だけど,Clojure の組み込み関数の名前は粋で洗練されたものが多い.これはひとえに言語の作者である Rich Hickey の卓抜な言語センスの賜物だ)

(some? nil)
;=> false

(some? false)
;=> true

(some? [])
;=> true

(some? "anything but nil")
;=> true

Clojure における等価性判定オペレータは = に統一されている.型ごとに = 相当のオペレータを割り当てている言語もあるが,Clojure ではとりあえず何でもかんでも = で判定できる.ありがたい.

(= 5 5 5)
;=> true

(= nil nil)
;=> true

(= 8 9)
;=> false

and / or

Clojure のブールオペレータとして andorがある.or はオペレーション内で最初に真と評価された値,もしくは最後の値を返す.and は最初に偽と評価された値を返すが,もしオペレーション内に偽となる値がなかった場合,最後に真と評価された値を返す.と言われてもなんのこっちゃ,という感じだと思うので,具体例を見てみよう.まずは or から.

(or nil false :the-first-truthy-value "the second truthy value")
;=> :the-first-truthy-value

(or (= 1 2) (= "fooly" "cooly"))
;=> false

最初の例で,nilfalse は両方とも偽となる値だ.そこで or はオペレーション内で最初に真と評価された(つまり nil でも false でもない) 値である:the-first-truthy-value を返した.次の例では,(= 1 2)false(= "fooly" "cooly")false なので,全体は (or false false) となる.false は偽なので,このオペレーション内のオペランドはすべて偽と評価されたことになる.ということで,最後尾の値である falseor オペレーション全体の評価結果として返されている.

続いて,and を見ていこう.

(and "Yang Wen-li" "Reinhard von Lohengramm" (= "Hilda" "Annerose") nil)
;=> false

(and :Final-Fantasy :Chrono-Trigger :Dragon-Quest :Dark-Souls)
;=> :Dark-Souls

最初の例で,最初に偽と評価されるのは (= "Hilda" "Annerose") だ.全体として,この評価結果である false が返される.皇帝の妻と皇帝の姉が同一人物であるという命題は偽,当然の結果だ.and は最初に偽と評価された値を返してそれ以降の評価をやめるので,この例で最後尾の nil は無視されている. 次の例では,and オペレーション内に偽となる値は存在しない.そこで,最後に評価された :Dark-Souls が返されている.挙げた他の作品と比べてダークソウルがことさら名作だと主張したいわけではもちろんない.

名付けと束縛

Clojure では,値に名前を束縛(bind)する時に,def を使う.束縛,というのは,他の言語でいうところの代入に近い概念で,関数型パラダイムを採用している言語では一般的な用語だ.変数に値を代入するのではなく,値に名前を束縛する.あまり難しく考える必要はないけれど,'代入' という言葉に引っ張られると後々の誤解,混乱の元となるかもしれないので,'束縛' という表現に慣れてほしい.とりあえず,def による束縛の一般形を見てみよう.

(def symbol value)

とてもシンプル.symbol 部分のシンボルが,value の値として束縛される.ここでも,(operator operands) というルールが保たれていることにも注目したい.def というオペレータを 2つのオペランドに適用しているだけで,値に名前を束縛できる.足し算や文字列の結合の時とのシンタックス的な違いはなく,書き方も共通.これが Lisp のパワーだ.では実際に def を使ってみよう.

(def major "Motoko Kusanagi")
;=>#'user/major

(def section-9 ["Aramaki" "Motoko" "Batou" "Togusa" "Ishikawa" "Borma" "Saito" "Pazu"])
;=>#'user/section-9

major
;=> "Motoko Kusanagi"

section-9
=> ["Aramaki" "Motoko" "Batou" "Togusa" "Ishikawa" "Borma" "Saito" "Pazu"]

ここで,さっき少し触れた '束縛' という表現について簡単に説明しておく.例えば Ruby で,複数の変数に値を代入して次のようなコードを書いたとする.

you_say = :stop
hello_goodbye = "I say YES, You say NO, You say "
if you_say == :stop
  hello_goodbye = hello_goodbye + "Stop, and I say Go Go Go!!!!! "
  else
  hello_goodbye = hello_goodbye + "LOVE lain LOVE lain LOVE lain LOVE lain LOVE lain"
end

むむむ.こんなふうに,変数代入というやり方で名前に紐づいた値を変更すると,プログラムの挙動を理解するのが難しくなる.なぜって,いちいち名前と値の関連を覚えておかなくてはいけないし,そもそもなぜその値が変わるべきなのかも知っていなくてはいけない.これはちょっと面倒だ.そしてあらゆる面倒はバグの元でもある.しかし,変数に代入,というアプローチではどうしてもこうした状況に陥りがちだ.そこで,関数型プログラミングの手法が鮮やかに活きてくる.Clojure に親しんでいくうちに,名前と値を絡めてしまう書き方から抜け出すいい方法が身に付くだろう.例えば,上のコードは Clojure でこんな風に書ける.

(defn hello-goodbye
  [you-say]
  (str "I say YES, You say NO, You say "
       (if (= you-say :stop)
         "Stop, and I say Go Go Go!!!!!"
         "LOVE lain LOVE lain LOVE lain LOVE lain LOVE lain")))

(hello-goodbye :stop)
;=> "I say YES, You say NO, You say Stop, and I say Go Go Go!!!!!"

(hello-goodbye :why)
;=> "LOVE lain LOVE lain LOVE lain LOVE lain LOVE lain"

ここでは,hello-goodbye という1引数の関数を定義している.仮引数であるyou-say の値によって分岐が決定され,分岐の決定がそのまま,返される文字列に対応する. 試しに :stopyou-say に束縛してみると,test-formの (= you-say :stop) が真になり,if の性質から if オペレーションの全体は,then-branch の "Stop, and I say Go Go Go!!!!!" と評価される.str オペレーションは

(str "I say YES, You say NO, You say " "Stop, and I say Go Go Go!!!!!")

となり,2つの文字列が結合され, "I say YES, You say NO, You say Stop, and I say Go Go Go!!!!!" という新たな文字列が生成されて,(hello-goodbye :stop) の評価結果として返される.

データ構造

Clojure ではすべてのデータ構造はイミュータブルだ.つまり,データ構造は基本的に変更できない.データ構造がミュータブル (mutable) な言語,例えば Ruby では,以下のようにフィールド上の変数を再代入するのはおなじみの方法だ.

section_9 = %[
  "Aramaki"
  "Motoko"
  "Batou"
]
section_9[0] = "Oyaji"

section_9
#=> [
#        "Oyaji"
#        "Motoko"
#        "Batou"
# ]

ここではインデックスを0から始まる自然数で指定し,その値を上書きすることで配列を変更している.Ruby など,手続き型と呼ばれるプログラミングパラダイムを採用した言語では,データ構造はミュータブル,つまり変更可能なオブジェクトである場合が多い.実際,上の例でも,section_9 の0番目の値は"Aramaki" から "Oyaji" に変更され,変更前のリストは実質的に破棄されている. Clojure には,こうしたオペレーションに相当するものはない.どうしてか,という説明はちょっと長くなりそうなので後回しにする.今のところは,細かいことを抜きにして,具体的なデータ構造をひとめぐりしよう.

nil

nilClojure のどんなデータ型でも取りうる値だ.般若心経でいうところの空に相当する,'無' を表す記号.言語によっては null と書かれることもあるが,実質はだいたい同じだ.制御フローのところでも言及したとおり,Clojure の条件分岐システムは nilfalse をベースに設計されている.分岐条件のテストで偽 (logical-falsity) と評価されるのは nilfalse だけ.他の値はすべて真 (logical-truth) となる.シーケンスプロトコル内での nil はセンチネルとしても使われる.センチネル (sentinel) とは,可変長データの終了地点を示すための予約値のことなんだけど,ときどき待ち行列で見かける'最後尾プレート' くらいに思っておけばいい.

数値

Longs

自然数のこと.なんで自然数が Long と呼ばれるかというと,立ち入った話になるので省略.整数演算の結果がものすごく大きな数になった場合,java.lang.ArithmeticException という例外が投げられることがある.そんな時のために Clojure+'-'*'inc'dec' といった演算子を用意している.こうした" ' "付き演算子は,オーバーフロー時に BigInt という型に自動で変換されるが,通常の演算子に比べるとパフォーマンス面で劣る.

Ratio

整数同士の比を表現する,いわゆる有理数 ('有理数' が誤訳って話は有名だよね).Clojure では,結果が整数にならない割り算は,まず有理数として表現される.

(/ 3 7)
;=> 3/7

Contagion

コーディングの文脈では見慣れない表現だけど,Clojure で Contagion と言えば,大きな整数(BigInt) と 浮動小数点数 (float/double) のこと.この性質によって,処理内に BigInt を含む演算の結果はすべて BigInt になり,float や double を含む演算の結果はすべて double となる.Contagion は "伝染" くらいに訳される堅い単語だけど,まさに値の性質が演算全体に伝染するというわけ.

(* 3.0 2)
;=> 6.0

(/ 3.0 7)
;=> 0.4285714285714285

BigInt / BigDecimal

数値リテラルとしてBigInt には N,BigDecimal には M がそれぞれのお尻にくっつく.

(class 96786801248216348816N)
;=> clojure.lang.BigInt

(class 3.14159265358M)
;=> java.math.BigDecimal

文字列

文字列は文字通り文字の列.他の多くの言語同様,文字列 (string) は文字のリストとして定義されている.一文字 (character) を明示的に扱いたい場合には,\a \B のように,各文字の直前に\をつければいい. 以下のサンプルは,文字列操作用ライブラリであるclojure.string 内の upper-case という関数を使っている.何をする関数かは,見ての通り.

(clojure.string/upper-case "serial experiments lain")
;=> "SERIAL EXPERIMENTS LAIN"

シーケンス

マップ

Clojure のマップは,他の言語でいう辞書や連想配列に相当するオブジェクトだ.ある値を別の値に紐づけて管理するための一般的な方法となる.そうそう,Clojure には map という非常に (非常に!) よく使う関数があって,それと区別するためにデータ構造としてのマップをあえてここではカタカナで表記している. Clojure は2種類のマップをサポートしているのだけど,ここではよりメジャーなハッシュマップに焦点を絞って説明する.では,具体的なマップリテラルをいくつか見てみよう.まずはこちら.

{}

空のマップだ.他の多くの言語同様,Clojure でもマップの表現に {} を使う. 次に,よく見る形を紹介しておこう.

{:unit-01 "Shinji"
 :unit-00 "Rei"}

単語の頭に ' : ' がついたリテラルはキーワードと呼ばれるもので,Clojure の中でも特にピーキーな実装になっていて面白いんだけど,面白いものは説明が長くなるのであと回し.ここで大事なのは,マップが鍵 (key) と値 (value) のペアで構成されていることだ.鍵にはキーワードの他に,文字列も使える.ということで,def を使って文字列を鍵にしたマップevaを定義し,シンジくんが鍵としての役割から逃げないか確かめてみる.

(def eva
  {"Shinji" 1
   "Rei" 0})
;=> #'user/eva

(eva "Shinji")
;=> 1

よくやったな,シンジ. ちなみに,マップの値に関して特に制限はない.文字列だろうが,数値だろうが,ヴェクタやセットなどの他のシーケンスだろうが,なんでもありだ.関数だって使える.あんまり使わないけど.もちろん,マップはマップを値に取れる.つまり,ネスト構造も表現できるようになっている.これを利用して,eva-pilot というマップを定義してみる.

(def eva-pilot
  {:unit-01 {:pilot "Shinji Ikari" :age 14 :character "nerd"} 
   :unit-00 {:pilot "Rei Ayanami" :age 14 :character "quiet"}})
;=> #'user/eva-pilot

マップリテラル {}を使う代わりに,hash-map 関数でもマップを生成できる.評価結果が記入順と異なっているのには理由があるけれど,これまた立ち入った話なので今は気にしなくていい.以下の例では鍵-値のペアが見やすいよう,間に ' , ' を挿入した評価結果が返されるけれど,基本はあくまでもスペースを区切り記号として使う.

(hash-map :unit-01 "Shinji" :unit-00 "Rei")
;=> {:unit-00 "Rei", :unit-01 "Shinji"}

マップ内の値は get を使って調べることができる.対応する鍵を見つけられなかった場合 getnil を返す.

(get {:unit-00 "Rei" :unit-01 "Shinji"} :unit-00)
;=>"Rei"

(get eva-pilot :unit-01)
;=> {:age 14, :pilot "Shinji Ikari", :character "nerd"}

(get eva-pilot :unit-02)
;=> nil

ネストしたリスト内の値は get-in とヴェクタを使って取得する.

(get-in eva-pilot [:unit-01 :age])
;=> 14

この例だと,get-in はまず :unit-01 を取得し,その内部の鍵 :age の値を返している.ヴェクタ内のキーワードの順序はそのままネストの深さに対応しているので,ひとつの階層からはひとつの鍵しか取得できない.とはいえ,鍵を順序よく並べていくだけで任意の階層にアクセスできるのは非常に便利な性質だ.便利な性質といえば,もうひとつ,マップではキーワードを関数として使うことができる.

キーワード

ここまで何度か目にしているキーワードを,この辺りでしっかり説明しておこう.すでに了解されていることだろうが,キーワードはこんな見た目をしている.

:key-word
:evangelion
:22

これらキーワードはカッコの最初で関数として用いることで,データ構造の中で自分自身に対応する値を呼び出すことができる.

(:unit-02 {:unit-01 "Shinji" :unit-00 "Rei" :unit-02 "Asuka"})
;=> "Asuka"

同じオペレーションは get を使うとこう書ける.

(get {:unit-01 "Shinji" :unit-00 "Rei" :unit-02 "Asuka"} :unit-02)
;=> "Asuka"

こうしてみると,get を使うよりもキーワード関数を使ったほうがより値に対して直接的で,書き方としても簡潔だ.とてもよく使うテクニックなので,今のうちに手に馴染ませておこう.

ヴェクタ

ヴェクタ (vector) は他の言語の配列 (array) に相当するもので,ヴェクタ内の各要素は0から始まる自然数をインデックスとして順序づけられている.ヴェクタはここまでの例に触れる中で何度か目にしているはず.マップと同様,ヴェクタもその要素になる値の種類に特別な条件はなく,どんなデータ型でも扱うことができる.

["serial" "experiments" "lain"]
[3 2 1]
[:a :b :c]
[{:Ikari {:father "Gendou" :son "Shinji"}} :evangelion 0 1 2]

先述の通り,インデックス番号からヴェクタ内の各要素にアクセスできる.get の出番だ.

(get ["serial" "experiments" "lain"] 2)
;=> lain

(get [{:Ikari {:father "Gendou" :son "Shinji"}} :evangelion 0 1 2] 0)
;=>{:Ikari {:son "Shinji", :father "Gendou"}}

ヴェクタに新たな要素を追加するにはconj を使う.このconjという関数,パフォーマンス上の問題で追加先のデータ構造によって挙動に揺れがある.ヴェクタの場合,要素は最後尾に追加される.

(conj [1 2 3] 4)
;=> [1 2 3 4]

また,vectorという関数でもヴェクタを作ることができる.

(vector "serial" "experiments" "lain")
;=> ["serial" "experiments" "lain"]

リスト

ヴェクタによく似たデータ構造としてリスト (list) がある.似ている,ということは違いがあるということで,まずは見た目が違う.

'(1 2 3 4)

それと,リストに対しては get が使えない.代わりに nth という関数が用意されている.

(nth '(:a :b :c :d) 0)
;=> :a

list 関数でリストを作成することもできる.

(list 1 2 3 4)
;=> (1 2 3 4)

ここで注意.REPLでリストを評価すると,返ってくるリストは頭のクオートが消えて,見た目には関数呼び出しのオペレーションと区別がつかない.もちろん,Clojure はこの差をしっかりと認識してくれるが,初見の際にはちょっと驚くと思うので言及しておいた.クオートが反映されない理由もあるにはあるが,現段階では進行の障りになるので省略する.

さて,リストに対して conj を使うと,要素はリストの先頭に追加される.

(conj '(1 2 3) 4)
;=> (4 1 2 3)

ここで問題がある.ヴェクタとリスト,お互いかなり似ているので,どうやって使い分けるかちょっと悩ましい.そこで,一つの指針を与えよう.つまり,先頭にすばやくデータを追加していきたい場合,それとマクロを書く場合にはリストを使う.それ以外では,ヴェクタを使った方が何かと便利な場合が多い.指針といってもこれは主観ではなくて,多くの Clojure プログラマが共有する知見だから安心して受け入れてほしい.

セット

セットはユニークな値のコレクションだ.ユニークな値っていうのはどういうことかというと,ひとつのセットの中には同じ値が存在しないということだ.そんなセットには2つの種類があるけれど,ここではよく目にするハッシュセットに絞って解説をする.

#{"Rei Ayanami" 14 :unit-00}

ハッシュセットはhash-set を使って作ることもできる.ついでに,値のセットにおける値のユニークネスについても確認しておこう.

(hash-set "Rei" "Rei" "Asuka")
;=> #{"Rei" "Asuka"}

このように,セットは内部に複数の同一値を持てない.そう,代わりの綾波なんていないんだ.その証拠に,このセットに conj で既存の値を追加しようとしても:

(conj #{"Rei" "Asuka"} "Rei")
;=> #{"Rei" "Asuka"}

と,素っ気なく無視される.

set を使えば,既存のヴェクタやリストをセット化することができる.

(set ["Rei" "Rei" "Rei" "Rei" :unit-00])
;=> #{:unit-00 "Rei"}

セット内に,ある値が存在するかどうかを調べるには contains? を使う.そのものズバリな名前だ.名前の最後が '?' で終わっているので,ブール値が返されることも分かる.

(contains?  #{"Rei" :unit-00} "Shinji")
;=> false

(contains?  #{"Rei" :unit-00} "Rei")
;=> true

無理やり同じ値をセットの中に詰めようとすると,シンタックスエラーが投げられてしまうので注意. セットでも,キーワードを関数として自身の呼び出しに使うことができる.もしセット内に値があれば,の話だけれど.無かったら,例によってnil が返される.

(:unit-00  #{"Rei" :unit-00})
;=> :unit-00

(:unit-01  #{"Rei" :unit-00})
;=> nil

セットにもget が使える.

(get  #{"Rei" :unit-00} :unit-00)
;=> :unit-00

(get #{"Rei" :unit-00} "Baka-Shinji")
;=> nil

けど,注意!セットには, nil を含むかどうか確認するために get を使うと,必ず nil を返してしまうという厄介な性質がある.混乱するといけないから,代わりに contains? を使うのが定石だ.

ここまで見てきた,マップ,ヴェクタ,リスト,セットなどのデータ構造は,まとめて 'シーケンス' と呼ばれる.Clojure にはこうしたシーケンスを自由自在に処理するための豊富な関数が用意されていて,実際,実践的な Clojure プログラミングの醍醐味はシーケンス処理をいかに使いこなすか,という点にある.と,言っても過言ではない.ここではシーケンスの実装と Java API に関する細かいことには立ち入らなかったが,気になる場合は clojure.org で逐次確認してみてほしい.

Routing with Compojure

What's Compojure

Compojure is a routing library for Ring interfaces. Compojure realise readable routing by the macro instead of the data. The components of Compojure are :

namespace usage
compojure.core provides some basic macros for routing
compojure.route provides some useful functions returning response
compojure.coercions provides route parameters
compojure.response provides render function which add some available types for :body

You are going to use compojure.core and compojure.route for now.

Routing with Compojure

Firstly, add Compojure to the dependencies in project.clj.

:dependencies [[org.clojure/clojure "1.10.0"]
               [ring "1.7.1"]
               [compojure "1.6.1"]]

After that, you can restart your REPL, then Leiningen would solve the dependencies automatically. Next, let's try to make some routings with Compojure. Add two Compojure namespaces, namely compojure.core and compojure.route to your current namespace.

(ns example-clj.core
  (:require [compojure.core :refer [defroutes context GET]]
            [compojure.route :as route]
            [ring.adapter.jetty :as server]
            [ring.util.response :as res]))
  1. defroutes,context, and GET are macros which were defined in compojure.core.
  2. ring.util.response has a bunch of functions to make various responses easily.

You can basically route with the macros in the compojure.core like GET and POST. Besides that, you can use various functions incompojure.route`.

(defn html [res]
  (res/content-type res "text/html; charset=utf-8"))
  1. Defining a function called "html" that takes HTTP response as a map.
  2. res/content-type takes a response-map and a value which will be a Content-Type in HTTP header, and set that value to Content-Type key in header in response-map.

Good. Well, now you should defn the home and index with their views and html functions.

;; > 1
(defn home
  [req]
  (-> (home-view req)
      res/response
      html))

;; > 2
(defn index
  [req]
  (-> (index-view req)
      res/response
      html))
  1. The function home takes request-map. -> is called thread-first macro which make code more readable by removing nesting.
    The above definition is equal to :
(defn home [req] (html (res/response (home-view req))))
  1. The definition of index almost the same as home. You know, thread-macro is an effective way to avoid the deep nesting.

More about Compojure

Here, we will take some examples and look around Compojure more in detail. As you can see from the code so far, the syntax for routing by Compojure is like this :

(GET "/" req home)

This kind of routing definition takes the request-map(req) and returns the Ring handler which returns response-map. It depends on the definitions of the HTTP method and the path whether Compojure execute the Ring handler or not. In this case, if the HTTP request is GET and the path is "/", then the Ring handler is executed. The compojure.core namespace has some macros which have same names of HTTP methods in Ring such as :

  • GET
  • POST
  • PUT
  • DELETE
  • OPTIONS
  • PATCH
  • HEAD

These macros can be used in actuality and when anything is fine, you can use ANY macro. The macros like GET takes 2 or more args. The paths for the first, the bindings for the second, and the responses generated by the binding for each after the third.

You can define the paths basically as string like "/" or "/index". In addition to this, you can specify special form including route parameter. In this way, any string can be accepted as a path in the part of: id up to the next "/" or ".". When you are going to use only numbers as a path, it is good to use regular expression like this :

(GET ["/index/:id" :id #"[0-9]+"] req index-show) 

The routing definitions can be grouped together by route function. route convert some routing definitions as handler to one Ring handler.

(def handler
  (routes ;; compojure.core/routes
   (GET "/" req home)
   (GET "/index" req index)
   (GET "/index/:id" [id] (index-show id))))

Each routing definitions are tested from top to bottom. If the route find the handler that returns some value (= not nil) then the handler will be executed. In addition to this, route can also contain the handler that is grouped by another route. So you can do this :

;; > 1
(defroutes main-routes
  (GET "/" req home)
  (route/not-found "<h1>404 page not found</h1>"))

;; > 2
(defroutes index-routes
  (context "/index" req
    (GET "/" req index)
    (GET "/new" req index-new)
    (context "/:id" [id]
      (GET "/" req (index-show id)))))

;; > 3
(defroutes handler
  (routes
   index-routes
   main-routes))

Pay attention to the new function named context. This context groups together the common part of the paths used in macros like GET. The context takes path, binding, and routing definition as first, second and third arguments respectively.

  1. defroute defines main-routes. GET macro takes "/" as a path, req as a binding, and home handler as a routing definition.
  2. defroute defines index-routes. context takes"/index" as a path, req as a binding, and it takes three elements after (GET "/" req index) as routing definitions.
  3. defroute defines handler. The order is a matter as the main-routes uses the not-found function that must return a non-nil value.

Summary

In this post, you have learned how to build the routing definition and wrap it by macros with Compojure. It's essential for every backend-web-development scenes to make routing correctly. Next, you need to get used to Hiccup, a library that provides some template DSL for generating HTML dynamically.