;; 名前 引数 本体
;; ----- ------ -------------------
(defn greet [name] (str "Hello, " name) )
Clojureは関数型言語だ。関数はファーストクラスであり、他の関数に渡したり他の関数から返したりすることができる。多くのClojureコードは主に(副作用のない)純粋関数で構成されているので、同じ入力で呼び出すと同じ出力が得られる。
defn
は名前付きの関数を定義する:
;; 名前 引数 本体
;; ----- ------ -------------------
(defn greet [name] (str "Hello, " name) )
この関数は単一の引数 name
を持つが、引数のベクターには任意個の引数を含めることができる。
関数名を「関数位置」(リストの最初の要素)に置いて関数を呼び出してみよう:
user=> (greet "students")
"Hello, students"
関数は異なる個数の引数(異なる「アリティ」(arity))をとるように定義することができる。異なるアリティはすべて同じ defn
の中に定義されなければならない―― defn
を複数回使うと以前の関数を置き換えてしまう。
個々のアリティは ([args*] body*)
というリストだ。あるアリティは別のアリティを呼び出すことができる。本体部(body)は任意個の式を含むことができ、戻り値は最後の式の結果だ。
(defn messenger
([] (messenger "Hello world!"))
([msg] (println msg)))
この関数は2つのアリティ(0引数と1引数)を宣言している。0引数のアリティは1引数のアリティをデフォルト値で呼び出して出力する。この関数を適切な個数の引数を渡して呼び出す:
user=> (messenger)
Hello world!
nil
user=> (messenger "Hello class!")
Hello class!
nil
関数は可変個の引数を定義することもできる――これは「可変長引数」関数として知られている。可変の引数は引数リストの末尾になければならない。この引数はシーケンスにまとめられており、関数から使うことができる。
可変の引数の始まりは &
で示される。
(defn hello [greeting & who]
(println greeting who))
この関数は引数 greeting
とリストにまとめられる who
という名前の可変個の引数(0個以上)をとる。このことは3引数で呼び出してみると分かる:
user=> (hello "Hello" "world" "class")
Hello (world class)
println
が who
を出力すると、まとめられた2要素のリストとして出力されることが分かる。
無名関数は fn
で作ることができる:
;; 引数 本体
;; --------- -----------------
(fn [message] (println message) )
無名関数には名前がないので、あとで参照することはできない。むしろ、無名関数は別の関数に渡す時点で作るのが典型的だ。
もしくは直ちに呼び出すことも可能だ(一般的な利用方法ではないが)。
;; 操作 (関数) 引数
;; -------------------------------- --------------
( (fn [message] (println message)) "Hello world!" )
;; Hello world!
ここではより大きな式の関数位置で無名関数を定義したが、その式は直ちに無名関数の式を引数で呼び出している。
多くの言語には文と式の両方があり、文は命令的に何かを行い値は返さないが、式は値を返す。Clojureには値を返す 式 だけがある。 if
のようなフロー制御の式でさえこれに含まれることはあとで見よう。
defn
vs fn
defn
は def
と fn
を縮約したものだと考えると便利かもしれない。 fn
が関数を定義し、 def
が関数を名前に束縛する。これらは等価だ:
(defn greet [name] (str "Hello, " name))
(def greet (fn [name] (str "Hello, " name)))
Clojureのリーダーに実装された fn
による無名関数のより短い形式がある: #()
。このシンタックスは引数リストを省略し、位置に基づいて引数を指定する。
%
は単一の引数に使う
%1
, %2
, %3
などは複数の引数に使う
%&
は任意の残りの(可変長)引数に使う
ネストした無名関数では引数が名付けられていないことで曖昧さが生じるため、ネストすることは認められていない。
;; これと等価: (fn [x] (+ 6 x))
#(+ 6 %)
;; これと等価: (fn [x y] (+ x y))
#(+ %1 %2)
;; これと等価: (fn [x y & zs] (println x y zs))
#(println %1 %2 %&)
apply
apply
関数は関数を0個以上の固定の引数で呼び出し、残りの必要な引数を最後のシーケンスから取り出す。最後の引数は 必ず
シーケンスでなければならない。
(apply f '(1 2 3 4)) ;; (f 1 2 3 4) と同じ
(apply f 1 '(2 3 4)) ;; (f 1 2 3 4) と同じ
(apply f 1 2 '(3 4)) ;; (f 1 2 3 4) と同じ
(apply f 1 2 3 '(4)) ;; (f 1 2 3 4) と同じ
これら4つの呼び出しはすべて (f 1 2 3 4)
と等価だ。 apply
は引数がシーケンスとして渡されてきたがそのシーケンス内の値で関数を呼び出さなければならない場合に便利だ。
例えば、こう書くのを避けるために apply
を使うことができる:
(defn plot [shape coords] ;; coords は [x y]
(plotxy shape (first coords) (second coords)))
代わりにシンプルに書くことができる:
(defn plot [shape coords]
(apply plotxy shape coords))
let
let
は「レキシカルスコープ」でシンボルを値に束縛する。レキシカルスコープは名前のための新たなコンテキストを作り、それは周囲のコンテキストの内側にネストされる。
let
で定義された名前は外側のコンテキストにおける名前に優先する。
;; 束縛 名前はここで定義されている
;; ------------ ----------------------
(let [name value] (code that uses name))
個々の let
は0個以上の束縛を定義し、本体に0個以上の式を持つことができる。
(let [x 1
y 2]
(+ x y))
この let
式は x
と y
の2つのローカル束縛を作っている。式 (+ x y)
は let
のレキシカルスコープ内にあり、xが1に、yが2に解決される。 let
式の外側では、xとyはすでに値に束縛されていない限り継続して意味を持つことはない。
(defn messenger [msg]
(let [a 7
b 5
c (clojure.string/capitalize msg)]
(println a b c)
) ;; let のスコープの終わり
) ;; 関数の終わり
messenger関数は msg
引数をとる。ここで defn
もまた msg
のレキシカルスコープを作っている―― messenger
関数の範囲内でだけ意味がある。
この関数のスコープの範囲内に let
は a
, b
, c
を定義する新たなスコープを作っている。let式のあとで a
を使おうとすれば、コンパイラがエラーを報告するだろう。
fn
特殊形式は「クロージャ」(closure)を作る。クロージャは周囲のレキシカルスコープ(上の msg
, a
, b
, c
など)を「閉じ込め」(close over)て、その値をレキシカルスコープを超えて捕捉する。
(defn messenger-builder [greeting]
(fn [who] (println greeting who))) ; greeting を閉じ込める
;; greeting はここで与えられて、スコープを外れる
(def hello-er (messenger-builder "Hello"))
;; hello-er がクロージャなので greeting の値は引き続き得られる
(hello-er "world!")
;; Hello world!
1) 引数をとらず「Hello」と出力する関数 greet
を定義しよう。 _ を実装で置き換えよう: (defn greet [] _)
2) greet
を最初は fn
特殊形式、次は #()
リーダーマクロとともに def
を使って再定義しよう。
;; fn を使う
(def greet __)
;; #() を使う
(def greet __)
3) 次のような関数 greeting
を定義しよう:
引数を与えられなければ"Hello, World!"を返す
1つの引数xを与えられると"Hello, x!"を返す
2つの引数xとyを与えられると"x, y!"を返す
;; ヒント 文字列を結合するのに str 関数を使う (doc str) (defn greeting ___) ;; テスト用 (assert (= "Hello, World!" (greeting))) (assert (= "Hello, Clojure!" (greeting "Clojure"))) (assert (= "Good morning, Clojure!" (greeting "Good morning" "Clojure")))
4) 単一の引数 x
をとってそのまま返す関数 do-nothing
を定義しよう。
(defn do-nothing [x] ___)
Clojureでは、これは identity
関数だ。それ自体ではidentityはあまり役に立たないが、高階関数を扱う際に必要になることがある。
(source identity)
5) 任意個の引数をとり、すべて無視して数 100
を返す関数 always-thing
を定義しよう。
(defn always-thing [__] ___)
6) 単一の引数 x
をとる関数 make-thingy
を定義しよう。この関数は、任意個の引数をとって常にxを返すような別の関数を返す必要がある。
(defn make-thingy [x] ___)
;; テスト
(let [n (rand-int Integer/MAX_VALUE)
f (make-thingy n)]
(assert (= n (f)))
(assert (= n (f 123)))
(assert (= n (apply f 123 (range)))))
Clojureでは、これは constantly
関数だ。
(source constantly)
7) 別の関数をとって引数なしで3回呼び出す関数 triplicate
を定義しよう。
(defn triplicate [f] ___)
8) 単一の引数 f
をとる関数 opposite
を定義しよう。この関数は、任意個の引数をとって f
を適用して結果に not
を呼び出すような別の関数を返す必要がある。Clojureの not
関数は論理否定をする。
(defn opposite [f]
(fn [& args] ___))
Clojureでは、これはcomplement関数だ。
(defn complement
"Takes a fn f and returns a fn that takes the same arguments as f,
has the same effects, if any, and returns the opposite truth value."
[f]
(fn
([] (not (f)))
([x] (not (f x)))
([x y] (not (f x y)))
([x y & zs] (not (apply f x y zs)))))
9) 別の関数と任意個の引数をとり、その関数を引数に3回適用する関数 triplicate2
を定義しよう。前にtriplicateの問題で定義した関数を再利用しよう。
(defn triplicate2 [f & args]
(triplicate ___))
10) java.lang.Math
クラス(Math/pow
, Math/cos
, Math/sin
, Math/PI
)を使って、次の数学的な事実を示そう:
piのコサインは-1
あるxについて sin(x)^2 + cos(x)^2 = 1
11) HTTP URLを文字列としてとり、WebからそのURLで取得した内容を文字列として返す関数を定義しよう。
ヒント:
java.net.URL
クラスとその openStream
を使う。そして内容を文字列として得るためにClojureの slurp
関数を使う。
(defn http-get [url]
___)
(assert (.contains (http-get "https://www.w3.org") "html"))
実は、Clojureの slurp
関数は引数をファイル名として解釈しようとする前に先にURLとして解釈する。単純化したhttp-getを書こう:
(defn http-get [url]
___)
12) 2つの引数をとる関数 one-less-arg
を定義しよう:
f
, 関数
x
, 値
そして x
と追加の引数に対して f
を呼び出すような別の関数を返す。
(defn one-less-arg [f x]
(fn [& args] ___))
Clojureでは、 partial
関数がこれをより一般化したものだ。
13) 2つの関数 f
と g
を引数としてとる関数 two-fns
を定義しよう。この関数は、1つの引数をとって g
を呼び出してからその結果に f
を呼び出した結果を返すような別の関数を返す。
つまり、その関数は f
と g
の合成を返す。
(defn two-fns [f g]
___)