Clojureの無名関数 [ 備忘録 ]


## 無名関数とは

無名関数（Anonymous Function）は、名前を持たない関数のことです。Clojureでは一時的な処理や、高階関数の引数として使用される関数を簡潔に記述するために使用されます。

## 基本的な書き方

Clojureで無名関数を作成する方法は主に2つあります。

### 1. `fn` フォームを使用する方法

```clojure
;; 基本形
(fn [引数] 処理)

;; 例：2つの数を足す無名関数
(fn [x y] (+ x y))

;; 使用例
((fn [x y] (+ x y)) 3 5)
;; => 8
```

### 2. リーダーマクロ `#()` を使用する方法（短縮記法）

**リーダーマクロとは**

リーダーマクロ（Reader Macro）は、Clojureのリーダーが特殊な文字や文字の組み合わせを見つけたときに、それを別の形式に変換する仕組みです。`#()` は無名関数を作成するためのリーダーマクロで、コンパイル時に `fn` フォームに変換されます。

```clojure
;; リーダーマクロ
#(+ %1 %2)

;; 上記は以下のように変換される
(fn [%1 %2] (+ %1 %2))
```

```clojure
;; 基本形
#(処理)

;; 例：2つの数を足す無名関数
#(+ %1 %2)
;; または
#(+ % %2)

;; 使用例
(#(+ %1 %2) 3 5)
;; => 8
```

## 引数の指定方法

### `fn` フォームでの引数指定

| 記法 | 説明 | 例 |
|------|------|-----|
| `[x]` | 単一引数 | `(fn [x] (* x 2))` |
| `[x y]` | 複数引数 | `(fn [x y] (+ x y))` |
| `[x & rest]` | 可変長引数 | `(fn [x & rest] (apply + x rest))` |
| `[]` | 引数なし | `(fn [] "Hello!")` |

### リーダーマクロでの引数指定

| 記法 | 説明 | 例 |
|------|------|-----|
| `%` または `%1` | 第1引数 | `#(* % 2)` |
| `%2` | 第2引数 | `#(+ %1 %2)` |
| `%3`, `%4`... | 第3引数以降 | `#(+ %1 %2 %3)` |
| `%&` | 残りの引数（可変長） | `#(apply + %&)` |

## 高階関数との組み合わせ

**高階関数とは**

高階関数（Higher-Order Function）とは、以下のいずれか（または両方）を満たす関数のことです：
1. 関数を引数として受け取る
2. 関数を戻り値として返す

Clojureの多くの組み込み関数（map、filter、reduce等）は高階関数であり、無名関数と組み合わせることで強力な処理を簡潔に記述できます。

### 基本的な高階関数

### map関数との組み合わせ

```clojure
;; 数値のリストを2倍にする
(map #(* % 2) [1 2 3 4 5])
;; => (2 4 6 8 10)

;; fn フォームで同じことを行う
(map (fn [x] (* x 2)) [1 2 3 4 5])
;; => (2 4 6 8 10)

;; より複雑な処理：偶数を2倍、奇数を3倍
(map #(if (even? %) (* % 2) (* % 3)) [1 2 3 4 5])
;; => (3 4 9 8 15)
```

### filter関数との組み合わせ

```clojure
;; 偶数のみを取り出す
(filter #(even? %) [1 2 3 4 5 6])
;; => (2 4 6)

;; 5より大きい数を取り出す
(filter #(> % 5) [1 3 7 2 9 4])
;; => (7 9)

;; 文字列の長さが3以上のものを取り出す
(filter #(>= (count %) 3) ["a" "abc" "hello" "x" "world"])
;; => ("abc" "hello" "world")
```

### reduce関数との組み合わせ
`reduce`は、コレクション（リスト、ベクター、シーケンスなど）の要素を順番に処理し、最終的に単一の値に集約する高階関数です。関数型プログラミングにおける「畳み込み」操作を行います。
```clojure
;; (reduce + [1 2 3 4])の場合
;; ステップ1: アキュムレータ = 1 (最初の要素)
;; ステップ2: (+ 1 2) = 3 (アキュムレータ = 3)
;; ステップ3: (+ 3 3) = 6 (アキュムレータ = 6)
;; ステップ4: (+ 6 4) = 10 (最終結果)
```

```clojure
;; 最大値を求める
(reduce #(if (> %1 %2) %1 %2) [3 7 2 9 1])
;; => 9

;; 文字列を連結する
(reduce #(str %1 "-" %2) ["a" "b" "c" "d"])
;; => "a-b-c-d"
```

### sort-by関数との組み合わせ

```clojure
;; 文字列の長さでソート
(sort-by #(count %) ["hello" "a" "world" "abc"])
;; => ("a" "abc" "hello" "world")

;; 数値の絶対値でソート
(sort-by #(Math/abs %) [-5 3 -1 7 -9])
;; => (-1 3 -5 7 -9)
```

## 複雑な無名関数の例

### ネストした処理

```clojure
;; マップから特定のキーの値を取得し、処理する
(map #(* (get % :value) 2) 
     [{:name "a" :value 10} 
      {:name "b" :value 20} 
      {:name "c" :value 30}])
;; => (20 40 60)

;; 条件分岐を含む処理
(map #(cond
        (< % 0) "negative"
        (= % 0) "zero"
        :else "positive")
     [-1 0 1 2 -3])
;; => ("negative" "zero" "positive" "positive" "negative")
```

### より高度な高階関数の例

#### 1. comp（関数合成）

```clojure
;; 複数の関数を合成する
(def process-number (comp #(* % 2) #(+ % 1) #(Math/abs %)))

(process-number -5)
;; => 12
;; 処理の流れ: -5 → abs → 5 → +1 → 6 → *2 → 12

;; map と組み合わせ
(map (comp #(* % % %) #(+ % 10)) [1 2 3])
;; => (1331 1728 2197)
;; 各要素に10を足してから3乗
```

#### 2. partial（部分適用）

```clojure
;; 引数の一部を固定した関数を作成
(def add-10 (partial + 10))
(add-10 5)  ;; => 15

;; filter と組み合わせ
(def greater-than-5 (partial < 5))
(filter greater-than-5 [1 3 7 2 9 4])
;; => (7 9)

;; より複雑な例
(def multiply-and-add 
  (fn [multiplier adder x] 
    (+ (* x multiplier) adder)))

(def double-and-add-1 (partial multiply-and-add 2 1))
(map double-and-add-1 [1 2 3 4])
;; => (3 5 7 9)
```

#### 3. apply（関数の適用）

```clojure
;; リストの要素を関数の引数として展開
(apply + [1 2 3 4 5])  ;; => 15

;; 無名関数と組み合わせ
(apply #(str %1 "-" %2 "-" %3) ["a" "b" "c"])
;; => "a-b-c"

;; 動的な関数適用
(defn apply-operation [op & args]
  (apply op args))

(apply-operation #(+ %1 %2) 10 20)  ;; => 30
(apply-operation #(str %1 %2) "Hello" " World")  ;; => "Hello World"
```

#### 4. 関数を返す関数（カリー化）

```clojure
;; 設定可能なバリデーター関数を作成
(defn make-validator [predicate error-msg]
  (fn [value]
    (if (predicate value)
      {:valid true :value value}
      {:valid false :error error-msg})))

(def positive-validator 
  (make-validator #(> % 0) "Value must be positive"))

(def non-empty-validator 
  (make-validator #(not (empty? %)) "Value must not be empty"))

(positive-validator 5)    ;; => {:valid true, :value 5}
(positive-validator -1)   ;; => {:valid false, :error "Value must be positive"}
(non-empty-validator "")  ;; => {:valid false, :error "Value must not be empty"}
```

#### 5. every-pred と some-fn（述語の組み合わせ）

```clojure
;; 複数の条件をすべて満たすかチェック
(def valid-adult? (every-pred #(>= % 18) #(<= % 120) integer?))
(valid-adult? 25)    ;; => true
(valid-adult? 15)    ;; => false
(valid-adult? 25.5)  ;; => false

;; いずれかの条件を満たすかチェック
(def weekend? (some-fn #(= % "Saturday") #(= % "Sunday")))
(weekend? "Saturday")  ;; => true
(weekend? "Monday")    ;; => nil
```

#### 6. iterate（反復適用）

```clojure
;; 関数を繰り返し適用してシーケンスを生成
(take 5 (iterate #(* % 2) 1))
;; => (1 2 4 8 16)

;; フィボナッチ数列
(defn fib-step [[a b]] [b (+ a b)])
(take 10 (map first (iterate fib-step [0 1])))
;; => (0 1 1 2 3 5 8 13 21 34)
```

### 高階関数の組み合わせパターン

#### パイプライン処理

```clojure
;; ->> マクロと高階関数を組み合わせたパイプライン
(defn process-data [numbers]
  (->> numbers
       (filter #(> % 0))           ; 正の数のみ
       (map #(* % %))              ; 平方
       (filter #(even? %))         ; 偶数のみ
       (reduce #(+ %1 %2) 0)))     ; 合計

(process-data [-2 -1 0 1 2 3 4 5])
;; => 20 (2²=4, 4²=16の合計)
```

#### 条件に応じた関数選択

```clojure
;; 条件に応じて異なる処理関数を選択
(defn select-processor [data-type]
  (case data-type
    :numbers   #(map (fn [x] (* x 2)) %)
    :strings   #(map (fn [s] (str s "!")) %)
    :booleans  #(map not %)
    identity)) ; デフォルトは何もしない

(def process-numbers (select-processor :numbers))
(process-numbers [1 2 3])  ;; => (2 4 6)

(def process-strings (select-processor :strings))
(process-strings ["hello" "world"])  ;; => ("hello!" "world!")
```

#### 関数のメモ化

```clojure
;; 計算結果をキャッシュする高階関数
(defn memoized [f]
  (let [cache (atom {})]
    (fn [& args]
      (if-let [cached-result (@cache args)]
        cached-result
        (let [result (apply f args)]
          (swap! cache assoc args result)
          result)))))

;; フィボナッチ数列を例に（重い計算の例）
(def fib
  (memoized
    (fn [n]
      (if (<= n 1)
        n
        (+ (fib (- n 1)) (fib (- n 2)))))))

(time (fib 35))  ; 初回は時間がかかる
(time (fib 35))  ; 2回目は即座に結果が返る
```

### 高階関数の実践的な活用例

#### データ変換パイプライン

```clojure
;; CSV風データの処理例
(def raw-data
  ["name,age,city"
   "Alice,25,Tokyo"
   "Bob,30,Osaka"
   "Carol,35,Tokyo"])

(defn parse-csv-line [line]
  (let [parts (clojure.string/split line #",")]
    {:name (first parts)
     :age (Integer/parseInt (second parts))
     :city (nth parts 2)}))

(->> raw-data
     (drop 1)                          ; ヘッダーを除去
     (map parse-csv-line)              ; パース
     (filter #(= (:city %) "Tokyo"))   ; 東京在住者のみ
     (map #(update % :age inc))        ; 年齢を1歳増やす
     (sort-by :age))                   ; 年齢順ソート
;; => ({:name "Alice", :age 26, :city "Tokyo"} 
;;     {:name "Carol", :age 36, :city "Tokyo"})
```

#### バリデーション関数の組み合わせ

```clojure
;; 複数のバリデーションを組み合わせる高階関数
(defn validate-with [validators]
  (fn [data]
    (reduce (fn [acc validator]
              (if (:valid acc)
                (validator (:value acc))
                acc))
            {:valid true :value data}
            validators)))

;; バリデーター関数群
(def required #(if (or (nil? %) (= % ""))
                 {:valid false :error "Required field"}
                 {:valid true :value %}))

(def min-length [n]
  (fn [value]
    (if (< (count (str value)) n)
      {:valid false :error (str "Minimum length is " n)}
      {:valid true :value value})))

(def max-length [n]
  (fn [value]
    (if (> (count (str value)) n)
      {:valid false :error (str "Maximum length is " n)}
      {:valid true :value value})))

;; バリデーターの組み合わせ
(def username-validator
  (validate-with [required 
                  (min-length 3) 
                  (max-length 20)]))

(username-validator "john")     ;; => {:valid true, :value "john"}
(username-validator "jo")       ;; => {:valid false, :error "Minimum length is 3"}
(username-validator "")         ;; => {:valid false, :error "Required field"}
```

| 項目 | 無名関数 | 名前付き関数 |
|------|----------|--------------|
| 定義方法 | `fn` または `#()` | `defn` |
| 再利用性 | 低い | 高い |
| 可読性 | 短い処理では良い | 複雑な処理では良い |
| 適用場面 | 一時的な処理、高階関数の引数 | 繰り返し使用する処理 |

## パフォーマンスに関する注意点

```clojure
;; ❌ ループ内で無名関数を毎回作成するのは非効率
(doseq [i (range 1000)]
  (map #(* % 2) some-collection))

;; ✅ 関数を事前に定義
(let [double-fn #(* % 2)]
  (doseq [i (range 1000)]
    (map double-fn some-collection)))

;; ✅ または名前付き関数を使用
(defn double-value [x] (* x 2))
(doseq [i (range 1000)]
  (map double-value some-collection))
```

## 実際のハンズオン練習

以下のコードを REPL で実行して確認してみましょう：

### 基本的な練習

```clojure
;; 1. 基本的な無名関数の作成と実行
((fn [x] (* x x)) 5)
(#(* % %) 5)

;; 2. リーダーマクロの変換確認
;; 以下は同じ意味
#(+ %1 %2)
(fn [%1 %2] (+ %1 %2))
```

### コレクション操作での活用

```clojure
(def numbers [1 2 3 4 5 6 7 8 9 10])

;; 平方を求める
(map #(* % %) numbers)

;; 奇数のみフィルタリング
(filter #(odd? %) numbers)

;; 累積和を求める
(reductions #(+ %1 %2) numbers)

;; 複数の操作を組み合わせ
(->> numbers
     (filter #(even? %))        ; 偶数のみ
     (map #(* % %))             ; 平方
     (filter #(> % 10)))        ; 10より大きいもの
```

### マップデータの処理

```clojure
(def people [{:name "Alice" :age 25 :city "Tokyo"}
             {:name "Bob"   :age 30 :city "Osaka"}
             {:name "Carol" :age 35 :city "Tokyo"}])

;; 東京在住者の名前を取得
(->> people
     (filter #(= (:city %) "Tokyo"))
     (map #(:name %)))

;; 年齢順でソート
(sort-by #(:age %) people)
```

### 高階関数の練習

```clojure
;; 1. comp（関数合成）の練習
(def process (comp #(* % 3) #(+ % 2) #(Math/abs %)))
(process -5)  ; -5 → 5 → 7 → 21

;; 2. partial（部分適用）の練習
(def add-100 (partial + 100))
(map add-100 [1 2 3 4 5])

;; 3. 関数を返す関数の練習
(defn make-greeting [prefix]
  #(str prefix ", " %))

(def say-hello (make-greeting "Hello"))
(def say-hi (make-greeting "Hi"))

(say-hello "World")  ; "Hello, World"
(say-hi "Alice")     ; "Hi, Alice"

;; 4. every-pred と some-fn の練習
(def valid-score? (every-pred number? #(>= % 0) #(<= % 100)))
(valid-score? 85)    ; true
(valid-score? -10)   ; false
(valid-score? "85")  ; false

(def weekend-or-holiday? 
  (some-fn #(= % "Saturday") 
           #(= % "Sunday") 
           #(= % "Holiday")))
(weekend-or-holiday? "Saturday")  ; true
(weekend-or-holiday? "Monday")    ; nil
```

### 実践的な練習問題

```clojure
;; 練習問題1: 学生データの処理
(def students 
  [{:name "田中" :math 80 :english 75 :science 90}
   {:name "佐藤" :math 95 :english 85 :science 88}
   {:name "鈴木" :math 70 :english 90 :science 75}])

;; 平均点を計算する関数を作成
(defn calculate-average [student]
  (let [scores [(:math student) (:english student) (:science student)]]
    (/ (apply + scores) (count scores))))

;; 平均点を追加して、平均点順にソート
(->> students
     (map #(assoc % :average (calculate-average %)))
     (sort-by :average >))

;; 練習問題2: 文字列処理のパイプライン
(def text "Hello World! This is a test. How are you?")

;; 単語数をカウントし、長い単語のみを抽出
(->> text
     (#(clojure.string/split % #"[\s\.\!\?]+"))  ; 単語に分割
     (filter #(not (empty? %)))                   ; 空文字を除去
     (filter #(> (count %) 3))                   ; 3文字より長い単語
     (map clojure.string/lower-case)             ; 小文字に変換
     (frequencies))                              ; 出現回数をカウント

;; 練習問題3: カスタム高階関数の作成
(defn make-filter-and-transform [predicate transformer]
  (fn [coll]
    (->> coll
         (filter predicate)
         (map transformer))))

;; 偶数を2倍にする関数
(def double-evens (make-filter-and-transform even? #(* % 2)))
(double-evens [1 2 3 4 5 6])  ; => (4 8 12)

;; 長い文字列を大文字にする関数  
(def uppercase-long-strings 
  (make-filter-and-transform #(> (count %) 3) clojure.string/upper-case))
(uppercase-long-strings ["a" "hello" "hi" "world"])  ; => ("HELLO" "WORLD")
```

## よくある間違いと対処法

### 1. リーダーマクロでのネスト

```clojure
;; ❌ リーダーマクロはネストできない
#(map #(* % 2) [1 2 3])  ; エラー

;; ✅ fn フォームを使用
#(map (fn [x] (* x 2)) [1 2 3])

;; ✅ または外側も fn フォームに
(fn [coll] (map (fn [x] (* x 2)) coll))
```

### 2. 引数の順序ミス

```clojure
;; ❌ 引数の順序が分かりにくい
(reduce #(if (> %2 %1) %2 %1) [1 5 3 9 2])

;; ✅ fn フォームで明示的に
(reduce (fn [max-val current] 
          (if (> current max-val) 
            current 
            max-val)) 
        [1 5 3 9 2])
```

## 応用パターン

### 部分適用的な使用

```clojure
;; 特定の値以上の要素をフィルタリングする関数を作成
(defn filter-greater-than [threshold]
  #(filter (fn [x] (> x threshold)) %))

(def filter-gt-5 (filter-greater-than 5))
(filter-gt-5 [1 3 7 2 9 4])  ;; => (7 9)
```

### 条件付き処理

```clojure
;; 条件に応じて異なる関数を適用
(defn apply-conditionally [predicate true-fn false-fn]
  #(if (predicate %)
     (true-fn %)
     (false-fn %)))

(def process-number 
  (apply-conditionally 
    even? 
    #(* % 2)    ; 偶数なら2倍
    #(+ % 10))) ; 奇数なら+10

(map process-number [1 2 3 4 5])  ;; => (11 4 13 8 15)
```

無名関数は Clojure プログラミングにおいて非常に重要な概念です。適切に使用することで、コードを簡潔で読みやすくすることができます。