;; name params body
;; ----- ------ -------------------
(defn greet [name] (str "Hello, " name) )
Clojureは関数型言語です。関数は第一級であり、他の関数に渡したり、他の関数から返したりすることができます。ほとんどのClojureコードは、主に純粋関数(副作用がない)で構成されているため、同じ入力で呼び出すと常に同じ出力が得られます。
defn
は名前付き関数を定義します。
;; name params body
;; ----- ------ -------------------
(defn greet [name] (str "Hello, " name) )
この関数は、単一のパラメータname
を持ちますが、パラメータベクターには任意の数のパラメータを含めることができます。
関数を呼び出すには、関数名の位置(リストの最初の要素)に関数名を指定します。
user=> (greet "students")
"Hello, students"
関数は、異なる数のパラメータ(異なる「引数」)を受け取るように定義できます。異なる引数はすべて同じdefn
で定義する必要があります。defn
を複数回使用すると、以前の関数が置き換えられます。
各引数は([param*] 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
で作成できます。
;; params body
;; --------- -----------------
(fn [message] (println message) )
無名関数には名前がないため、後で参照することはできません。むしろ、無名関数は通常、別の関数に渡される時点で作成されます。
または、すぐに呼び出すことも可能です(これは一般的な用法ではありません)。
;; operation (function) argument
;; -------------------------------- --------------
( (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
などは、複数のパラメータに使用されます。
%&
は、残りの(可変引数)パラメータに使用されます。
ネストされた無名関数は、パラメータに名前がないため、あいまいさが発生するため、ネストは許可されていません。
;; Equivalent to: (fn [x] (+ 6 x))
#(+ 6 %)
;; Equivalent to: (fn [x y] (+ x y))
#(+ %1 %2)
;; Equivalent to: (fn [x y & zs] (println x y zs))
#(println %1 %2 %&)
apply
apply
関数は、0個以上の固定引数を使用して関数を呼び出し、残りの必要な引数を最後のシーケンスから取得します。最後の引数は必ずシーケンスである必要があります。
(apply f '(1 2 3 4)) ;; same as (f 1 2 3 4)
(apply f 1 '(2 3 4)) ;; same as (f 1 2 3 4)
(apply f 1 2 '(3 4)) ;; same as (f 1 2 3 4)
(apply f 1 2 3 '(4)) ;; same as (f 1 2 3 4)
これらの4つの呼び出しはすべて(f 1 2 3 4)
と同等です。apply
は、引数がシーケンスとして渡されているが、シーケンス内の値を使用して関数を呼び出す必要がある場合に便利です。
たとえば、apply
を使用して、これを記述することを回避できます。
(defn plot [shape coords] ;; coords is [x y]
(plotxy shape (first coords) (second coords)))
代わりに、次のように簡単に記述できます。
(defn plot [shape coords]
(apply plotxy shape coords))
let
let
は、「レキシカルスコープ」で記号を値にバインドします。レキシカルスコープは、名前の新しいコンテキストを作成し、周囲のコンテキスト内にネストされます。let
で定義された名前は、外側のコンテキストの名前よりも優先されます。
;; bindings name is defined here
;; ------------ ----------------------
(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)
) ;; end of let scope
) ;; end of function
messenger関数はmsg
引数を受け取ります。ここで、defn
はmsg
のレキシカルスコープも作成しています。これはmessenger
関数内でのみ意味を持ちます。
その関数スコープ内で、let
はa
、b
、およびc
を定義するための新しいスコープを作成します。let式の後にa
を使用しようとすると、コンパイラはエラーを報告します。
fn
特殊形式は「クロージャ」を作成します。これは、周囲のレキシカルスコープ(上記のmsg
、a
、b
、またはc
など)を「クローズオーバー」し、その値をレキシカルスコープを超えてキャプチャします。
(defn messenger-builder [greeting]
(fn [who] (println greeting who))) ; closes over greeting
;; greeting provided here, then goes out of scope
(def hello-er (messenger-builder "Hello"))
;; greeting value still available because hello-er is a closure
(hello-er "world!")
;; Hello world!
1) 引数を取らずに「Hello」と出力する関数greet
を定義します。___
を実装に置き換えます: (defn greet [] _ _ _)
2) def
を使用して、最初にfn
特殊形式を使用して、次に#()
リーダマクロを使用して、greet
を再定義します。
;; using fn
(def greet __)
;; using #()
(def greet __)
3) 次の関数greeting
を定義します。
引数が指定されていない場合は、「Hello, World!」を返します。
1つの引数xが指定されている場合は、「Hello, x!」を返します。
2つの引数xとyが指定されている場合は、「x, y!」を返します。
;; Hint use the str function to concatenate strings
(doc str)
(defn greeting ___)
;; For testing
(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] ___)
;; Tests
(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では、これは補完関数です。
(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
)を使用して、次の数学的事実を実証します。
円周率のコサインは-1です
一部のxについて、sin(x)^2 + cos(x)^2 = 1
11) HTTP URLを文字列として受け取り、そのURLをWebから取得し、コンテンツを文字列として返す関数を定義します。
ヒント: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]
___)