2015/04/09

Clojureのcond->とcond->>は、とっても便利だと思うんですよ

突然で申し訳ないのですけど、私は、以下のコードが嫌いです。

(defn tv-volume
  "TVの音量は偶数で!"
  [x]
  (if (odd? x)
    (inc x)
    x))

嫌いな理由は、以下の通り。

  • xを4回も書かなければならない。タイプミスの危険性がある。
  • ifelseを書き忘れ、nilが返って不正な動きをする危険性がある。

で、そんな時はやっぱり、cond->cond->>なわけですよ。

(defn tv-volume
  "TVの音量は偶数。特定の場合に特定の操作で、それ以外はそのままにしたいなら、cond->かcond->>で!"
  [x]
  (cond-> x
    (odd? x) (inc)))

cond->は、条件が真になった場合は指定された処理を->を経由して呼び出します。上のコードの場合は、(odd? x)な場合は(-> x (inc))になるわけで、だから結果として(inc x)が実行されるというわけ。xを書く数は3回に減ったし、elseの書き忘れがないので意図しないnilが返る危険性もありません。

というわけで、cond->cond->>はとっても便利だと思うんですよ。もし使ったことがないなら、使ってみませんか?


2015/04/09

ifが式で関数がファースト・クラス・オブジェクトな幸せを噛みしめる

条件が多段になる場合のコードって、書くのが難しいと思うんですよ。

成人式を例にして考えてみます。群馬県の成人式はバンジー・ジャンプで栃木県の成人式はライオンと格闘、それ以外の都道府県では普段通りの平穏無事な日々を送るとしましょう。とりあえず言語はJavaでいってみます。

if (person.getAge() == 20) {
  switch (person.getBirthplace()) {
  case "群馬県":
    doBungeeJumping();
    break;

  case "栃木県":
    fightWithLions();
    break;

  default:
    peacefulDays();  // 重複している!
  }
} else {
  peacefulDays();    // 重複している!
}

peacefulDays()が重複していますので、このコードは悪いコードです。今の程度なら問題はなさそうですけれど、たとえば成人式以外のイベントも対応しようとすると日付チェックが追加されるので条件式が3段になるわけです。そうなると、たぶんどこかでpeacefulDays()を書くのを忘れて、とても見つけづらいバグが発生してしまう。嫌だなぁ……。

でも、Clojureなら、こんな場合も大丈夫です。だって、「ifが式」で「関数がファースト・クラス・オブジェクト」なのですから。

Clojureのifは、値を返す式です。Javaの三項演算子と同じ。そして、Clojureの関数はファースト・クラス・オブジェクトなので、値として扱える。だから、以下のようなコードを書けます。

((or (if (= (:age human) 20)
       (cond
         (= (:birthplace human) "群馬県") do-bungee-jumping
         (= (:birthplace human) "栃木県") fight-with-lions))
     peaceful-days)))

ifの部分が何をしているのかというと、20歳で群馬県出身ならdo-bungee-jumpingという「関数」を、同様に20歳で栃木現出身ならfight-with-lionsという「関数」を返しています。で、ifcondは条件が一致しない場合はnilを返すようになっていて、Clojureではnilは偽だから、その場合のorの部分の結果はpeaceful-daysという「関数」になります。

で、一番外側の ()で関数が呼び出されるので、peaceful-daysが1つしかなくても、Javaの場合と同じ動作になるわけです。

ほら、ifが式で関数がファースト・クラス・オブジェクトな幸せを噛みしめたくなってきたでしょ?

P.S.

もしこのシステムを将来に渡って保守しなければならないなら、幸せを噛みしめる前に以下のようなコードにしたほうが良いかも。

(defn born-in?
  [place person]
  (= (:birthplace person) place))

(defn age-is?
  [age person]
  (= (:age person) age))

(def gummar?
  (partial born-in? "群馬県"))

(def tochigian?
  (partial born-in? "栃木県"))

(def initiation?
  (partial age-is? 20))

(def preds-and-actions
  [[(every-pred gummar? initiation?)    do-bungee-jumping]
   [(every-pred tochigian? initiation?) fight-with-lions]
   [(constantly true)                   peaceful-days]])

(defn suitable-action-for
  [person]
  (some (fn [[pred action]]
          (and (pred person) action))
        preds-and-actions))

((suitable-action-for person))

あと、Javaの場合も、Java 8で追加されたラムダ式を使うなら、綺麗に書けるかもしれません。

#文法が面倒くさくて、途中で挫折しちゃいました……。


Newer Page: 6 of 6