英和訓練

著作権は原文に属します

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. 論理的偽である値はfalsenilでありそれだけだ。その他の全ての値は論理的真である。

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" recursion recurは、古典的な再帰である
    • 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 リデューサは、関数合成としてイテレートを表す
    • Added in Clojure 1.5, not covered here Clojure 1.5で追加された。ここでは説明しない。

loop and recur loopとrecur

  • Functional looping construct 関数的なループの構成部品である
    • loop defines bindings loopにより束縛を定義する
    • recur re-executes loop with new bindings recurloopを新たな束縛とともに再び実行する
  • 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 position recurにおいて、全ての束縛シンボルは位置によって値が与えられねばならない
    • Loop bindings つまりloopの束縛の値と
    • defn/fn arguments defnないしfnの引数の値とが該当する
  • Recursion via recur does not consume stack recurを用いた再帰は、(呼び出し)スタックを消費しない

Exceptions 例外

Exception handling 例外処理

  • try/catch/finally as in Java Javaにおけるのと同様のtrycatchfinallyが使える
(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"))

原文: https://clojure.org/guides/learn/flow