Learn Clojure - Flow Control フロー制御
Statements vs. Expressions 文と式との違い
In Java, expressions return values, whereas statements do not. Javaでは、式は値を返し、文は値を返さない。
// "if" is a statement because it doesn't return a value: // 「if」は値を返さないので文である。 String s; if (x > 10) { s = "greater"; } else { s = "greater or equal"; } obj.someMethod(s); // Ternary operator is an expression; it returns a value: // 三項演算子は値を返すので式である。 obj.someMethod(x > 10 ? "greater" : "greater or equal");
In Clojure, however, everything is an expression! ClojureではJavaとは違い全ては式だ! Everything returns a value, and a block of multiple expressions returns the last value. 全てが値を返す。複数の式からなるブロックは最後の式の値を返す。Expressions that exclusively perform side-effects return nil
. 副作用のためだけの式はnil
を返す。
Flow Control Expressions フロー制御の式
Accordingly, flow control operators are expressions, too! 従って、フロー制御演算子らも式である!
Flow control operators are composable, so we can use them anywhere. フロー制御演算子は式の集まりであるから、フロー制御演算子をどこにでも置ける。This leads to less duplicate code, as well as fewer intermediate variables. これによってコードの重複は減るし、介在する変数の数も減る。
Flow control operators are also extensible via macros, which allow the compiler to be extended by user code. マクロを使うことでフロー制御演算子を拡張できる。マクロは、ユーザのコードによってコンパイラを拡張できる機能である。We won't be discussing macros today, but you can read more about them at Macros, Clojure from the Ground Up, or Clojure for the Brave and True, among many other places. ここでマクロを説明することはしないが、これら3点他、マクロを説明している資料はいくつも存在する。
if
if
is the most important conditional expression - it consists of a condition, a "then", and an "else". 最も重要な条件式はif
である。if
は、条件式とthen節とelse節からなる。if
will only evaluate the branch selected by the conditional. その条件式が真ならthen節だけが、偽ならelse節だけが評価される。
user=> (str "2 is " (if (even? 2) "even" "odd")) 2 is even user=> (if (true? false) "impossible!") ;; else is optional else節は省略可能だ nil
Truth 真理
In Clojure, all values are logically true or false. Clojureにおいては全ての値は論理的真か論理的偽かである。The only "false" values are false
and nil
- all other values are logically true. 論理的偽である値はfalse
とnil
でありそれだけだ。その他の全ての値は論理的真である。
user=> (if true :truthy :falsey) :truthy user=> (if (Object.) :truthy :falsey) ; objects are true オブジェクトは真 :truthy user=> (if [] :truthy :falsey) ; empty collections are true 空のコレクションは真 :truthy user=> (if 0 :truthy :falsey) ; zero is true ゼロは真 :truthy (if false :truthy :falsey) :falsey (if nil :truthy :falsey) :falsey
if and do ifとdo
The if
only takes a single expression for the "then" and "else". if
はthen節とelse節にそれぞれ1つの式しか取らない。Use do
to create larger blocks that are a single expression. 複数の式をブロックとして1つの式にまとめたい時にはdo
を使う。
Note that the only reason to do this is if your bodies have side effects! (Why?) なお、そうする必要が生じるのは、if式の本体部に副作用がある場合だけである。
(if (even? 5) (do (println "even") true) (do (println "odd") false))
when
when
is an if
with only a then
branch. whenはthen節への分岐のみ持つifである。It checks a condition and then evaluates any number of statements as a body (so no do
is required). whenは条件式を確認し、本体部にある任意個の式を評価する。(よってdoを使う必要性は生じない。) The value of the last expression is returned. 最後の式の値が返る。If the condition is false, nil is returned. そもそも条件式が偽だったらnilが返る。
when
communicates to a reader that there is no "else" branch. when
を使うことは、コードの読み手に対してelse節がないことを表現する意味がある。
(when (neg? x) (throw (RuntimeException. (str "x must be positive: " x))))
cond
cond
is a series of tests and expressions.cond
は条件判定と式の並びだ。Each test is evaluated in order and the expression is evaluated and returned for the first true test. 各条件式が順に評価され、最初に真になった条件に対応する式が評価されてその値が返る。
(let [x 5] (cond (< x 2) "x is less than 2" (< x 10) "x is less than 10"))
cond and else condとelse
If no test is satisfied, nil is returned. 真になる条件がなかったならnilが返る。A common idiom is to use a final test of :else
. よく用いられるイディオムとして、最後の条件式を:else
とするものがある。Keywords (like :else
) always evaluate to true so this will always be selected as a default. (この:else
のように)キーワードは全て真に評価されるので、デフォルトで選ばれる分岐を表せる。
(let [x 11] (cond (< x 2) "x is less than 2" (< x 10) "x is less than 10" :else "x is greater than or equal to 10"))
case
case
compares an argument to a series of values to find a match. case
は、引数を取ってそれとマッチする値を、値の並びから探す。This is done in constant (not linear) time! これは(線形時間ではなく)定数時間で行われる! However, each value must be a compile-time literal (numbers, strings, keywords, etc). ただし、それぞれの値はコンパイル時のリテラル、すなわち、数、文字列、キーワードなどでなければならない。
Unlike cond
, case
will throw an exception if no value matches. cond
とは異なり、どの値もマッチしなかったならcase
は例外を投げる。
user=> (defn foo [x] (case x 5 "x is 5" 10 "x is 10")) #'user/foo user=> (foo 10) x is 10 user=> (foo 11) IllegalArgumentException No matching clause: 11
case with else-expression else式のあるcase
case
can have a final trailing expression that will be evaluated if no test matches. case
には、どの判定もマッチしなかった場合に評価されるべき最後尾の式を置くことができる。
user=> (defn foo [x] (case x 5 "x is 5" 10 "x is 10" "x isn't 5 or 10")) #'user/foo user=> (foo 11) x isn't 5 or 10
Iteration for Side Effects 副作用を目的としたイテレート
dotimes
- Evaluate expression n times 式をn回評価する
- Returns
nil
nilを返す
user=> (dotimes [i 3] (println i)) 0 1 2 nil
doseq
- Iterates over a sequence シーケンス上をイテレートする
- If a lazy sequence, forces evaluation 遅延シーケンスの場合は評価を強制する
- Returns
nil
nilを返す
user=> (doseq [n (range 3)] (println n)) 0 1 2 nil
doseq with multiple bindings 複数の束縛を伴ったdoseq
- Similar to nested
foreach
loops ネストしたforeachループに似ている - Processes all permutations of sequence content シーケンスの内容の全ての順列を処理する
- Returns
nil
nilを返す
user=> (doseq [letter [:a :b] number (range 3)] ; list of 0, 1, 2 (prn [letter number])) [:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2] nil
Clojure's for Clojureのfor
- List comprehension, not a for-loop リスト内包表記であり、forループではない
- Generator function for sequence permutation シーケンスの順列についてのジェネレータ関数である
- Bindings behave like
doseq
束縛はdoseq
を用いた場合と同様に動作する
user=> (for [letter [:a :b] number (range 3)] ; list of 0, 1, 2 [letter number]) ([:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2])
Recursion 再帰
Recursion and Iteration 再帰とイテレート
- Clojure provides recur and the sequence abstraction Clojureには
recur
と、シーケンス抽象化がある recur
is "classic" recursionrecur
は、古典的な再帰である- Consumers don't control it, considered a lower-level facility より低層が提供する機能であるから、利用者がこれをコントロールしない
- Sequences represent iteration as values シーケンスは値としてイテレートを表す
- Consumers can partially iterate 利用者は部分的にのみイテレートできる
- Reducers represent iteration as function composition リデューサは、関数合成としてイテレートを表す
loop and recur loopとrecur
- Functional looping construct 関数的なループの構成部品である
loop
defines bindingsloop
により束縛を定義するrecur
re-executesloop
with new bindingsrecur
はloop
を新たな束縛とともに再び実行する
- Prefer higher-order library functions instead 可能なら、loopとrecurよりも、ライブラリにある高階関数を使うべきである
(loop [i 0] (if (< i 10) (recur (inc i)) i))
defn and recur defnとrecur
- Function arguments are implicit
loop
bindings 関数の引数は暗黙的なloop
の束縛である
(defn increase [i] (if (< i 10) (recur (inc i)) i))
recur for recursion 再帰呼び出しにrecurを使う
recur
must be in "tail position"recur
は末尾になければならない- The last expression in a branch すなわち、分岐の中で最後に評価される式でなければならないということだ
recur
must provide values for all bound symbols by positionrecur
において、全ての束縛シンボルは位置によって値が与えられねばならない- Loop bindings つまり
loop
の束縛の値と - defn/fn arguments
defn
ないしfn
の引数の値とが該当する
- Loop bindings つまり
- Recursion via
recur
does not consume stackrecur
を用いた再帰は、(呼び出し)スタックを消費しない
Exceptions 例外
Exception handling 例外処理
(try (/ 2 1) (catch ArithmeticException e "divide by zero") (finally (println "cleanup")))
Throwing exceptions 例外を投げる
(try (throw (Exception. "something went wrong")) (catch Exception e (.getMessage e)))
with-open
(let [f (clojure.java.io/writer "/tmp/new")] (try (.write f "some text") (finally (.close f)))) ;; Can be written: 次のようにも書ける (with-open [f (clojure.java.io/writer "/tmp/new")] (.write f "some text"))