こちらは、Clojure Advent Calendar 2018 12日目の記事です。
昨日は、@iku000888さんの「Clojure/conj 2018 参加してきた」でした。
こんにちは、はじめまして。システムエンジニアの@na4daです。昨日分の記事を投稿された@iku000888さんに「せっかくClojureを始めてちょうど1年ということなら、是非とも@na4daさんの記事を読んでみたい」ということで急遽お隣を開けていただきました。ありがとうございます。
経緯
私は元々、別の会社に新卒として入社し、SEとは違う仕事をしていました。ですが諸事情により前職を辞め、転職でSEとしてXcooに入社し、現在に至ります。Clojureは現職に就いてから初めて触った言語で、そこから今月で丸1年経ちました。ということで、ゼロからClojureを1年使ってきて感じたこと、「これはすごい!」と思ったことなどをお伝えしたいと思います。
ちなみに、プログラミングの経験は、大学時代にC言語、Java、HTMLに触れた程度で、研究室ではLaTeXで卒論を書く傍らMATLABを書いていました。また、Lisp系言語に関しては触ったことがありませんでした。
おそらく、各項目の内容は調べればすぐに出てくるはずで、稚拙な内容になるかもしれませんが、よろしければどうかお付き合いくださいませ。
sequence関係
Clojureを書いててかなりの頻度で使うsequence関係の関数は、やはり画期的だなと思います。おかげさまで「一見するとfor文を使いそうな処理」をすっきりと書くことができています。ただ、感覚的に慣れるまで多少時間が必要でした。今の所「繰り返し処理→繰り返したいデータをあらかじめ一列に並べて処理に流す」ようなイメージを持ちながら書いています。map
はもちろんのこと、結果をベクタで返すmapv
や、特定の条件に合致するデータのみを返すfilter
もよく使っています。
(map inc (range 1 10))
;;=> (2 3 4 5 6 7 8 9 10)
(mapv dec (range 1 10))
;;=> [0 1 2 3 4 5 6 7 8]
(filter some? [1 2 nil 3])
;;=> (1 2 3)
また、nil
を除いて結果を返してくれるkeep
も便利な関数の一つだと思います。これを知るまでは(map f coll)
(remove nil? coll)
という流れで書いていたので、無駄に遠回りしていた部分が省けて助かりました(以下の例だとfilter even? ...
と書いた方が短いですが…)。
(keep #(if (even? %) %) (range 1 10))
;;=> (2 4 6 8)
ちなみに、この例で用いている#
および%
は、無名関数を手軽に作れるリーダーマクロとして、こちらも大変重宝しています。
スレッディングマクロの素晴らしさ
Clojureを書き始めて最も利便性を感じたマクロの1つです。あるデータに対して関数を3つ4つ与えていくと、通常ならどうしても横に間延びしがちなのですが、この各々の関数を順番に並べて書くことができるお馴染みのマクロですね。
(conj (rest (butlast (reverse [1 2 3 4 5]))) 5)
;; => (5 4 3 2)
;; same as below...
(-> [1 2 3 4 5]
reverse
butlast
rest
(conj 5))
;; => (5 4 3 2)
ここぞというところで登場させることでコードが読みやすくなるのはとてもありがたいと思います。私の場合は、上記の例のように、関数ごとに改行することでコードが縦にスッキリと整い、各関数名と関数が実行される順序の把握が容易にできるように書くことが多いです。
終端の引数に対して用いたい場合は->>
となりますが、取れる引数が1つの場合にも使えるので、2つのパターンが混合しているときは->>
でまとめてしまう書き方をよくやっています。また、このマクロのニュアンスを取り入れた関数として、任意の位置の引数に対応できるas->
や、途中でnil
になった時点でそれ以降の関数をスキップするsome->
(もしくはsome->>
)なども、ここぞということろで役立っています。
カラフルでアクロバティックな関数
デフォルトで用意されている関数は、多彩なボキャブラリーに即したネーミングになっていて、その多くは実にアクロバティックな動作をしていると思います。
- 同じものが連続しているデータを取り除く = 個々に独立したものだけを取り出す
distinct
- 任意の関数に与えた各結果を一つのデータにまとめる
reduce
- keyとvalそれぞれを与えて一つのmapを生成する
zipmap
- 各々の関数に対して並列させる意のjuxtaposeに由来する
juxt
などなど…
特にjuxt
はかなり強力な関数だと、使ってみて改めて思います。単一のデータ対して様々な関数を一度に並列で適用させたい時に素晴らしい効果を発揮しますね。私の場合は、あるデータのvalidationを実装する際に、juxt
で各検査の結果を一つのベクタで受け取るように書いていました。また、以下の例のように、juxt
以降に並べる各関数を、改行を利用して縦に並べて表記することもできるため、見通しがとても良くなる点も良いなと思います。
(def test-map
{:a 1, :b 2, :c 3, :d 4, :e 5})
((juxt keys
vals
:a
:c
#(assoc % :e 6)) test-map)
;; => [(:a :b :c :d :e) (1 2 3 4 5) 1 3 {:a 1, :b 2, :c 3, :d 4, :e 6}]
多種多様なライブラリ
オープンソースとして公開されているライブラリが大変豊富なのも魅力的だなと思う点の一つです。必要なライブラリをrequire
する事で、おそらく大抵のことはでClojureで実装できるのではと思っています。
データの入出力周りを仕事で扱うことが多いので、例えば、CSVの入出力を行うdata.csvや、JSON形式からClojureのデータ形式にparseしてくれるcheshire、Clojure越しにクエリを与えてデータベースの操作ができるhugsqlなどは、よく利用させていただいています。以下の例では、cheshire
でマップのserialize/deserializeを行なっています。
(require `cheshire.core)
(def test-map
{:a 1, :b 2, :c 3, :d 4, :e 5})
(cheshire.core/generate-string test-map)
;; => "{\"a\":1,\"b\":2,\"c\":3,\"d\":4,\"e\":5}"
(def test-serialized
"{\"a\":1,\"b\":2,\"c\":3,\"d\":4,\"e\":5}")
(cheshire.core/parse-string test-serialized)
;; => {"a" 1, "b" 2, "c" 3, "d" 4, "e" 5}
(cheshire.core/parse-string test-serialized true)
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
柔軟なbinding
let
内で行う変数束縛のうち、分配束縛が特に便利でよく使っているのですが、引数宣言でもそれができるという点は、Clojureに慣れてくるのと比例して、便利に感じるようになりました。上手く使えば、わざわざlet
を用いる必要がなくなるため、括弧の階層を一つ減らして書くことができますし、マップを引数に取って任意のvalueを束縛する時に、キーをそのまま変数名にできる:keys
の効果も存分に発揮できると思います。関数内で新しく変数名を与える(考える)必要がないというのもありがたいです。
(def test-map
{:a 1, :b 2, :c 3, :d 4, :e 5})
(defn sum-a-b-c
[{:keys [a b c]}]
(+ a b c))
(sum-a-b-c test-map)
;; => 6
お世話になっているサイト
-
ClojureDocs
言わずと知れたドキュメントサイトです。投稿されている使用例で直感的に関数の内容が理解できるのがありがたいです。 -
Thesaurus
こちらも有名な類語辞典です。ありきたりにならない関数名や変数名を考えるヒントをここから得たりしています。 -
Allacronyms
任意の単語の頭文字語や略語の候補を検索できるサイトです。コンパクトな変数名を考える際に一役買っています。ただ、やりすぎて不自然にならないように注意したいところです。
おわりに
経緯の項でお気付きの方もいるかもしれませんが、私はこのClojureで初めて関数型言語に触れた
ということでもあります。初めは、鎖のように関数が連鎖して処理が進んでいく流れを掴むのが大変だったように思います。ですが、必要な変数を必要なタイミングで用意し、必要なことだけを行うように関数を作るというのは、無駄が無く、読みやすいコードを書きやすいことに加え、メンテナンス性に対しても非常に理にかなった方法だと思います。まだまだ勉強中の身ではありますが、1年書いてみて、Clojureは良い言語だなと改めて感じています。
実はClojureと並行してClojureScriptも1年近く触れていますが、記事にできるほど慣れていないため、今回は割愛させていただきます。
ここまでお読みいただき、ありがとうございました。
これからClojureを始めてみたい、興味があるという方への参考に、少しでもなれば幸いです。