夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

Clojure勉強日記(その3 2.2 リーダマクロ/2.3関数

こんにちは。 では、さっそく実際のコードを書いてみましょう。

今回の範囲は「2.2 リーダマクロ」「2.3 関数」です。
この辺を突破すれば多少は読めるのではないかと期待しながら入ります。。。

2.2 リーダマクロ

リーダマクロ≠マクロであり、
Clojureの構文認識の際に特定の前置きマクロ文字によって起動されるリーダの特殊処理。
例としてはコメント。
セミコロンが「前置きマクロ文字」となり、「この行の最後までのすべてを無視する」というリーダマクロが起動される。
他の例としては「クオート文字(')」。
これは「評価を抑制する」というリーダマクロを起動する。

通常、文字列が入力された場合Clojureはそれを評価して他のオブジェクトに置き換えようとする。
だが、頭にクオート文字が付けられた場合はそれを行わない。単なる文字列として扱う。
実際、下記のように頭にクオートをつけた文字列は頭にコロンをつけた文字列やダブルクオートで囲った
文字列のように評価されず、そのままの文字列として扱われる。動作としては関数「quote」と同じ。
何も付けないと評価されて例外が出る。

user=> 'Test
Test
user=> :Test
:Test
user=> "Test"
"Test"
user=> Test
CompilerException java.lang.RuntimeException: Unable to resolve symbol: Test in
this context, compiling:(NO_SOURCE_PATH:0:0)
user=> (symbol? (quote Test))
true
user=> (quote Test)
Test

尚、LISPと違ってClojureはリーダマクロをユーザ側で新規定義することはできない。

2.3 関数

Clojureでは関数呼び出しは単に最初の要素が関数であるようなリスト。

; str関数、Test、スペース、Helloという順番で指定
user=> (str "Test" " " "Hello")
"Test Hello"

Clojureの慣習として、複数後からなる関数名はclear-agent-errorsのようにハイフンでつながれる。
→ このあたり、Camel形式のJavaと区別するために明示的にこうされているようですね。
関数が述語であればクエスチョンマークで名前を終えるのが慣習。
例えば、string?(文字列かどうかを判定)、keyword?(キーワードかどうかを判定)、symbol?(シンボルかどうかを判定)のように。

1.関数の定義方法

関数を定義するにはdefnを使用する。

user=> (defn hello "Returns the form 'Hello, username.'"
    [username]
    (str "Hello, " username))
#'user/hello

実行してみると引数が足りなかったり余計だったりするとエラーになる。

user=> (hello 'Test)
"Hello, Test"
user=> (hello "Test")
"Hello, Test"
user=> (hello :Test)
"Hello, :Test"
user=> (hello )
ArityException Wrong number of args (0) passed to: user$hello  clojure.lang.AFn.
throwArity (AFn.java:437)
user=> (hello :Test :Hoge)
ArityException Wrong number of args (2) passed to: user$hello  clojure.lang.AFn.
throwArity (AFn.java:437)

その場合は関数定義内で引数がない場合はデフォルト引数を指定して実行するなどの対処を取ればいい。
1関数定義内でオーバーロードを一通り済ませるようなノリの模様。

引数リストに&を含めることで可変長引数を定義することが可能。

user=> (defn hello-multi
    "Returns the from 'Hello, usename and XXX persons'"
    ([] (hello-multi "world"))
    ([username & persons] (str "Hello, " username " and " (count persons) " persons"))
    )
#'user/hello-multi
user=> (hello-multi )
"Hello, world and 0 persons"
user=> (hello-multi :Test :Test)
"Hello, :Test and 1 persons"
user=> (hello-multi :Test :Test :Test)
"Hello, :Test and 2 persons"
2.無名関数

fnによって無名関数を作ることができる。
無名関数を用いる用途としては3点。

  • 1.関数本体が短いために定義することがかえってコードを読みにくくする場合

例として、文字列要素のフィルタリングを考える。
通常の関数定義を用いると記述形式は下記の通り。

user=> (defn target-word? [word] (> (count word) 2))
#'user/target-word?
user=> (use '[clojure.contrib.str-utils :only (re-split)])
nil
user=> (doc re-split)
-------------------------
clojure.contrib.str-utils/re-split
([pattern string] [pattern string limit])
  Splits the string on instances of 'pattern'.  Returns a sequence of
  strings.  Optional 'limit' argument is the maximum number of
  splits.  Like Perl's 'split'.
nil
user=> (filter target-word? (re-split " " "A fine day it is"))
ClassCastException java.lang.String cannot be cast to java.util.regex.Pattern
lojure.contrib.str-utils/re-split (str_utils.clj:28)
user=> (filter target-word? (re-split #"\W+" "A fine day it is"))
("fine" "day")

無名関数は(fn [params*] body)と記述する。
無名関数を使うと下記のように記述出来る。

user=> (filter (fn [w] (> (count w) 2)) (re-split #"\W+" "A fine day it is"))
("fine" "day")

暗黙の引数名を使用したさらに短い無名関数の記述(#(body))もある。
引数は%1、%2といったように名づけられる。最初の引数を%で指定可能。
上記の記述を使用すると下記のように記述できる。
下記のように第一引数=% or %1で取れる模様。0オリジンでないあたりは素直かも。

user=> (filter #(> (count %) 2) (re-split #"\W+" "A fine day it is"))
("fine" "day")
user=> (filter #(> (count %1) 2) (re-split #"\W+" "A fine day it is"))
("fine" "day")
  • 2.ある関数内部でだけ使用されるため名前を関数の中だけにとどめたい場合

let形式を用いることで、名前「target-word?」に無名関数をバインドすることができる。
JavaScriptでも変数に関数を渡すことができるが、同じようなノリだと思われる。というかそのままだが。

user=> (defn target-words [text]
    (let [target-word? #(> (count %) 2)]
    (filter target-word? (re-split #"\W+" text))))
#'user/target-words
user=> (target-words "A fine day it is")
("fine" "day")

target-word? は意味を持つが、かといって他の場所で使えるほど汎用的な関数では無い。
そのため内部だけに隠ぺいする・・・という方針を取る場合上記のように用いる。
無名関数に名前をつけたい場合上記のように使用すればいいように見える。

  • 3.関数自体を生成する場合

今までは無名関数を「使用」し値を返していたが、
そもそも関数自体を返り値として用いることもできる。
返り値として使用する場合、名称を付けることはできないため必然的に無名関数を用いることになる。

user=> (defn make-hellofunc [hello-prefix]
    (fn [username] (str hello-prefix ", " username)))
#'user/make-hellofunc
user=> (defn hey-hello (make-hellofunc "Hey"))
IllegalArgumentException Parameter declaration make-hellofunc should be a vector
  clojure.core/assert-valid-fdecl (core.clj:6732)
user=> (def hey-hello (make-hellofunc "Hey"))
#'user/hey-hello
user=> (hey-hello "Test")
"Hey, Test"

上記のように、make-hellofunc関数を用いて関数を返り値として返すことができる。
但し「make-hellofunc関数を用いて作成した関数」については単なる変数扱いとなるため
defnによる関数バインドは出来ない模様。defでバインドする。
尚、下記のようにそもそも変数にバインドせずに実行させることもできる。

user=> ((make-hellofunc "Hi") "Test")
"Hi, Test"

それぞれのhellofunc生成関数は自分が作られたときに与えられた[hello-prefix]を覚えており、
[hello-prefix]の値を用いて処理を行っている・・・という位置づけとなる。
上記のようなhellofunc生成関数を[hello-prefix]を包み込むクロージャと呼ぶ模様。

無名関数の使いどころとしては、「短く、別の場所で呼ばれることがない」場合となるが、
わかりにくくなるケースもある。
そのため、必要なら使えるというだけで使わなくてはならないというわけでもない。
このあたりはそれなりになれるのに時間がかかりそうではある。

ともあれ、今回はここまでです。
無名関数や変数への関数バインドについてはJavaScriptで扱ったことがある概念のため、
理解しやすくはありましたね。
では、また次回に。