Clojureのスレッディングマクロ [ 備忘録 ]

スレッディングマクロは、ネストした関数呼び出しを線形の関数呼び出しフローに変換し、コードの可読性を向上させる強力な機能です。

## 基本概念

### assoc関数とupdate関数の理解

まず、スレッディングマクロを理解する前に、よく使用される基本的な関数について説明します。

| 関数 | 説明 | 使用例 |
|------|------|--------|
| `assoc` | マップに新しいキー・値のペアを追加 | `(assoc {:a 1} :b 2)` → `{:a 1, :b 2}` |
| `update` | マップの既存の値を関数で変更 | `(update {:age 30} :age inc)` → `{:age 31}` |

```clojure
;; assocの例
(def person {:name "太郎"})
(assoc person :age 25)
;; => {:name "太郎", :age 25}

;; updateの例  
(def person {:name "太郎", :age 30})
(update person :age inc)  ; incは1を足す関数
;; => {:name "太郎", :age 31}
```

## 問題の発見：ネストした関数の読みにくさ

以下の関数を見てください：

```clojure
(defn transform [person]
  (update (assoc person :hair-color :gray) :age inc))

(transform {:name "ソクラテス", :age 39})
;; => {:name "ソクラテス", :age 40, :hair-color :gray}
```

この関数は以下の処理を行います：
1. `person`に`:hair-color :gray`を追加
2. その結果の`:age`を1つ増やす

しかし、コードを読むときは内側から外側へ読む必要があり、直感的ではありません。

## thread-first マクロ（->）

`->`マクロは値を左から右に「スレッド」（通す）します。

### 基本的な使い方

```clojure
(defn transform* [person]
  (-> person
      (assoc :hair-color :gray)
      (update :age inc)))
```

### 動作原理

`->`マクロは、各式の**最初の引数**として値を挿入します：

```clojure
;; 通常の書き方
(defn transform* [person]
  (-> person
      (assoc :hair-color :gray)
      (update :age inc)))

;; 挿入位置を,,,で示した場合（視覚的理解のため）
(defn transform* [person]
  (-> person
      (assoc ,,, :hair-color :gray)    ; personが最初の引数に挿入
      (update ,,, :age inc)))          ; 前の結果が最初の引数に挿入

;; 実際に展開されるコード
(defn transform* [person]
  (update (assoc person :hair-color :gray) :age inc))
```

### 段階的な実行の流れ

```clojure
;; ステップバイステップの実行
(-> {:name "太郎", :age 30}
    (assoc ,,, :hair-color :gray)    ; ステップ1
    (update ,,, :age inc))           ; ステップ2

;; ステップ1の実行結果
(assoc {:name "太郎", :age 30} :hair-color :gray)
;; => {:name "太郎", :age 30, :hair-color :gray}

;; ステップ2の実行結果  
(update {:name "太郎", :age 30, :hair-color :gray} :age inc)
;; => {:name "太郎", :age 31, :hair-color :gray}
```

### 実践例

```clojure
;; 単純な関数のチェーン
(-> {:name "田中"}
    (assoc ,,, :age 25)
    (assoc ,,, :city "東京")
    (update ,,, :age #(+ % 5)))
;; => {:name "田中", :age 30, :city "東京"}

;; 実際の実行順序
;; 1. (assoc {:name "田中"} :age 25) 
;;    => {:name "田中", :age 25}
;; 2. (assoc {:name "田中", :age 25} :city "東京")
;;    => {:name "田中", :age 25, :city "東京"}  
;; 3. (update {:name "田中", :age 25, :city "東京"} :age #(+ % 5))
;;    => {:name "田中", :age 30, :city "東京"}

;; キーワードや関数名だけの場合
(-> {:hair-color "black"} :hair-color name clojure.string/upper-case)
;; 挿入位置を示すと
(-> {:hair-color "black"} 
    (:hair-color ,,,)           ; キーワード関数として実行
    (name ,,,)                  ; 前の結果"black"を変換
    (clojure.string/upper-case ,,,))  ; "BLACK"に変換
;; => "BLACK"

;; より複雑なデータ構造の操作
(-> {:user {:profile {:settings {:theme "dark"}}}}
    (assoc-in ,,, [:user :profile :name] "太郎")
    (update-in ,,, [:user :profile :settings] assoc :language "ja")
    (get-in ,,, [:user :profile]))
;; => {:name "太郎", :settings {:theme "dark", :language "ja"}}
```

## thread-last マクロ（->>）

`->>`マクロは値を**最後の引数**として挿入します。

### 使用場面

シーケンス操作関数（`map`, `filter`, `reduce`など）は通常、処理対象のシーケンスを最後の引数に取ります。

```clojure
;; ネストした呼び出し（読みにくい）
(defn calculate []
  (reduce + (map #(* % %) (filter odd? (range 10)))))

;; ->>を使用（読みやすい）
(defn calculate* []
  (->> (range 10)           ; [0 1 2 3 4 5 6 7 8 9]
       (filter odd? ,,,)    ; 挿入位置：最後の引数
       (map #(* % %) ,,,)   ; 挿入位置：最後の引数
       (reduce + ,,,)))     ; 挿入位置：最後の引数

;; 実際の展開結果
(defn calculate* []
  (reduce + (map #(* % %) (filter odd? (range 10)))))

;; ステップバイステップの実行
;; 1. (range 10) => [0 1 2 3 4 5 6 7 8 9]
;; 2. (filter odd? [0 1 2 3 4 5 6 7 8 9]) => [1 3 5 7 9]  
;; 3. (map #(* % %) [1 3 5 7 9]) => [1 9 25 49 81]
;; 4. (reduce + [1 9 25 49 81]) => 165
```

### 実践例

```clojure
;; 単語のカウント
(->> ["apple" "banana" "apple" "cherry" "banana" "apple"]
     (group-by identity ,,,)
     (map (fn [[k v]] [k (count v)]) ,,,)
     (into {} ,,,))
;; => {"apple" 3, "banana" 2, "cherry" 1}

;; 実行ステップ
;; 1. (group-by identity ["apple" "banana" "apple" "cherry" "banana" "apple"])
;;    => {"apple" ["apple" "apple" "apple"], "banana" ["banana" "banana"], "cherry" ["cherry"]}
;; 2. (map (fn [[k v]] [k (count v)]) {上記の結果})
;;    => (["apple" 3] ["banana" 2] ["cherry" 1])
;; 3. (into {} (["apple" 3] ["banana" 2] ["cherry" 1]))
;;    => {"apple" 3, "banana" 2, "cherry" 1}

;; 数値処理の例
(->> [1 2 3 4 5 6 7 8 9 10]
     (filter even? ,,,)           ; 偶数のみ
     (map #(* % 3) ,,,)          ; 3倍する
     (filter #(> % 10) ,,,)      ; 10より大きいもの
     (reduce + ,,,))             ; 合計
;; => 78

;; 実行ステップ
;; 1. (filter even? [1 2 3 4 5 6 7 8 9 10]) => [2 4 6 8 10]
;; 2. (map #(* % 3) [2 4 6 8 10]) => [6 12 18 24 30]
;; 3. (filter #(> % 10) [6 12 18 24 30]) => [12 18 24 30]
;; 4. (reduce + [12 18 24 30]) => 84

;; 文字列処理の例
(->> "Hello World From Clojure"
     (clojure.string/split ,,, #" ")
     (map clojure.string/lower-case ,,,)
     (filter #(> (count %) 4) ,,,)
     (clojure.string/join "-" ,,,))
;; => "hello-world-clojure"
```

## どちらを使うべきか？

| 場面 | 使用するマクロ | 理由 |
|------|----------------|------|
| データ構造操作 | `->` | `assoc`, `update`, `dissoc`などは対象を最初の引数に取る |
| シーケンス操作 | `->>` | `map`, `filter`, `reduce`などは対象を最後の引数に取る |
| Java相互運用 | `->` | Javaオブジェクトは最初の引数として渡される |

```clojure
;; データ構造操作の例（->を使用）
(-> {:name "山田"}
    (assoc ,,, :age 30)
    (update ,,, :age inc)
    (dissoc ,,, :temp-field))
;; 挿入位置は常に最初の引数

;; シーケンス操作の例（->>を使用）
(->> [1 2 3 4 5]
     (filter even? ,,,)
     (map #(* % 2) ,,,)
     (reduce + ,,,))
;; 挿入位置は常に最後の引数

;; Java相互運用の例（->を使用）
(-> "Hello World"
    (clojure.string/lower-case ,,,)
    (.replace ,,, "world" "clojure")
    (.substring ,,, 0 5))
;; => "hello"
```

## as->マクロ：柔軟な位置指定

場合によっては、値を異なる位置に挿入したい場合があります。

```clojure
(as-> [:foo :bar] v
  (map name v)      ; vは最後の位置：(map name [:foo :bar])
  (first v)         ; vは最後の位置：(first ["foo" "bar"])
  (.substring v 1)) ; vは最初の位置：(.substring "foo" 1)
;; => "oo"

;; 挿入位置を,,,で示すと
(as-> [:foo :bar] v
  (map name ,,, v)      ; 実際は(map name v)として実行
  (first ,,, v)         ; 実際は(first v)として実行  
  (.substring v ,,, 1)) ; 実際は(.substring v 1)として実行
```

### 詳細な実行ステップ

```clojure
;; ステップバイステップの実行
(as-> [:foo :bar] v
  (map name v)          ; [:foo :bar] → ["foo" "bar"]
  (first v)             ; ["foo" "bar"] → "foo"
  (.substring v 1))     ; "foo" → "oo"

;; より複雑な例：データ変換パイプライン
(as-> {:users [{:name "太郎" :age 30} {:name "花子" :age 25}]} data
  (:users data)                           ; データからusersを取得
  (map #(update % :age inc) data)         ; 各ユーザーの年齢を+1
  (filter #(> (:age %) 26) data)         ; 26歳以上のユーザーのみ
  (group-by #(> (:age %) 30) data)       ; 30歳以上で分類
  (get data true)                         ; 30歳以上のグループを取得
  (mapv :name data))                      ; 名前のみ取得
;; => ["太郎"]

;; 実際の実行ステップ
;; 1. (:users {:users [...]}) => [{:name "太郎" :age 30} {:name "花子" :age 25}]
;; 2. (map #(update % :age inc) [...]) => [{:name "太郎" :age 31} {:name "花子" :age 26}]
;; 3. (filter #(> (:age %) 26) [...]) => [{:name "太郎" :age 31} {:name "花子" :age 26}]
;; 4. (group-by #(> (:age %) 30) [...]) => {true [{:name "太郎" :age 31}], false [{:name "花子" :age 26}]}
;; 5. (get {...} true) => [{:name "太郎" :age 31}]
;; 6. (mapv :name [...]) => ["太郎"]

;; 文字列処理での混合位置指定
(as-> "Hello, Beautiful World!" text
  (clojure.string/split text #" ")        ; 文字列を分割
  (map clojure.string/upper-case text)    ; 各単語を大文字に
  (clojure.string/join "-" text)          ; ハイフンで結合 (textは2番目の位置)
  (.replace text "BEAUTIFUL" "AMAZING")   ; 置換 (textは1番目の位置)
  (subs text 0 13))                       ; 部分文字列 (textは1番目の位置)
;; => "HELLO-AMAZING"
```

### 実践例

```clojure
(as-> "hello world" s
  (clojure.string/split s #" ")     ; ["hello" "world"]
  (map clojure.string/upper-case s) ; ["HELLO" "WORLD"]
  (clojure.string/join "-" s))      ; "HELLO-WORLD"
```

## some->とsome->>：nil安全なスレッディング

`nil`が途中で現れた場合に処理を停止します。

```clojure
;; 従来の方法（冗長）
(when-let [counter (:counter a-map)]
  (inc (Long/parseLong counter)))

;; some->を使用（簡潔）
(some-> a-map :counter Long/parseLong inc)
;; 挿入位置を示すと
(some-> a-map 
        (:counter ,,,)      ; nilの場合ここで停止
        (Long/parseLong ,,,) ; nilの場合ここで停止  
        (inc ,,,))          ; nilの場合ここで停止
```

### 詳細な動作例

```clojure
;; 成功ケース
(some-> {:counter "42"}
        (:counter ,,,)          ; "42"を取得
        (Long/parseLong ,,,)    ; 42に変換
        (inc ,,,))              ; 43にインクリメント
;; => 43

;; 失敗ケース1：キーが存在しない
(some-> {:other-key "value"}
        (:counter ,,,)          ; nilを返す → ここで停止
        (Long/parseLong ,,,)    ; 実行されない
        (inc ,,,))              ; 実行されない  
;; => nil

;; 失敗ケース2：パースエラー
(some-> {:counter "not-a-number"}
        (:counter ,,,)          ; "not-a-number"を取得
        (Long/parseLong ,,,)    ; 例外が発生するが、some->では捕捉されない
        (inc ,,,))
;; => NumberFormatException

;; some->>の例
(some->> [1 2 3 4 5]
         (map inc ,,,)          ; [2 3 4 5 6]
         (filter even? ,,,)     ; [2 4 6] 
         (first ,,,))           ; 2
;; => 2

;; nilが混入した場合
(some->> nil
         (map inc ,,,)          ; 実行されない
         (filter even? ,,,)     ; 実行されない
         (first ,,,))           ; 実行されない
;; => nil
```

### 実践例

```clojure
;; 安全なチェーン処理
(some-> {:user {:profile {:name "太郎"}}}
        (:user ,,,)
        (:profile ,,,)
        (:name ,,,)
        (clojure.string/upper-case ,,,))
;; => "太郎"

;; 実行ステップ
;; 1. (:user {:user {:profile {:name "太郎"}}}) => {:profile {:name "太郎"}}
;; 2. (:profile {:profile {:name "太郎"}}) => {:name "太郎"}
;; 3. (:name {:name "太郎"}) => "太郎"
;; 4. (clojure.string/upper-case "太郎") => "太郎"

;; キーが存在しない場合
(some-> {:user {}}
        (:user ,,,)             ; {} (空のマップ)
        (:profile ,,,)          ; nil → ここで停止
        (:name ,,,)             ; 実行されない
        (clojure.string/upper-case ,,,))  ; 実行されない
;; => nil (例外は発生しない)

;; 従来の書き方との比較
;; 従来の書き方（冗長）
(when-let [user (:user data)]
  (when-let [profile (:profile user)]
    (when-let [name (:name profile)]
      (clojure.string/upper-case name))))

;; some->を使用（簡潔）
(some-> data :user :profile :name clojure.string/upper-case)

;; 実用的な例：APIレスポンスの処理
(defn extract-user-email [api-response]
  (some-> api-response
          (:data ,,,)
          (:user ,,,)
          (:contact ,,,)
          (:email ,,,)
          (clojure.string/lower-case ,,,)))

(extract-user-email {:data {:user {:contact {:email "USER@EXAMPLE.COM"}}}})
;; => "user@example.com"

(extract-user-email {:data {:user {}}})  ; emailが存在しない
;; => nil
```

## cond->とcond->>：条件付きスレッディング

条件に基づいて処理を適用するかどうかを決定します。

```clojure
(defn describe-number [n]
  (cond-> []
    (odd? n)  (conj ,,, "奇数")     ; 条件が真の場合のみ実行
    (even? n) (conj ,,, "偶数")     ; 条件が真の場合のみ実行
    (zero? n) (conj ,,, "ゼロ")     ; 条件が真の場合のみ実行
    (pos? n)  (conj ,,, "正数")))   ; 条件が真の場合のみ実行

(describe-number 3)  ;; => ["奇数" "正数"]
(describe-number 4)  ;; => ["偶数" "正数"] 
(describe-number 0)  ;; => ["偶数" "ゼロ"]
(describe-number -2) ;; => ["偶数"]

;; 実行の流れ（describe-number 3の場合）
;; 初期値: []
;; (odd? 3) => true  → (conj [] "奇数") => ["奇数"]
;; (even? 3) => false → スキップ
;; (zero? 3) => false → スキップ  
;; (pos? 3) => true  → (conj ["奇数"] "正数") => ["奇数" "正数"]
```

### 詳細な実行例

```clojure
;; cond->の詳細な動作
(cond-> {:name "太郎"}
  true                    (assoc ,,, :processed true)
  (= "太郎" (:name ,,,))  (assoc ,,, :japanese true)  ; 注意：,,,は条件部では使えない
  false                   (assoc ,,, :never-added true))

;; 正しい書き方
(let [person {:name "太郎"}]
  (cond-> person
    true                      (assoc :processed true)
    (= "太郎" (:name person)) (assoc :japanese true)
    false                     (assoc :never-added true)))
;; => {:name "太郎", :processed true, :japanese true}

;; cond->>の例
(cond->> [1 2 3 4 5]
  true        (map inc ,,,)           ; [2 3 4 5 6]
  (> 5 3)     (filter even? ,,,)      ; [2 4 6] 
  false       (map #(* % 10) ,,,)     ; スキップされる
  true        (reduce + ,,,))         ; 12
;; => 12

;; より実用的な例：設定に基づくデータ処理
(defn process-data [data {:keys [sort? reverse? limit]}]
  (cond->> data
    sort?     (sort ,,,)
    reverse?  (reverse ,,,)  
    limit     (take limit ,,,)))

(process-data [3 1 4 1 5 9 2 6] {:sort? true :reverse? true :limit 3})
;; => (9 6 5)

;; 実行ステップ
;; 1. sort? が true → (sort [3 1 4 1 5 9 2 6]) => [1 1 2 3 4 5 6 9]
;; 2. reverse? が true → (reverse [1 1 2 3 4 5 6 9]) => (9 6 5 4 3 2 1 1)
;; 3. limit が 3 → (take 3 (9 6 5 4 3 2 1 1)) => (9 6 5)
```

### 実践例

```clojure
;; ユーザー情報の構築
(defn build-user [base-user admin? premium?]
  (cond-> base-user
    admin?   (assoc ,,, :role :admin)
    premium? (assoc ,,, :subscription :premium)
    true     (assoc ,,, :created-at (java.time.Instant/now))))

(build-user {:name "田中"} true false)
;; => {:name "田中", :role :admin, :created-at #inst"..."}

;; より複雑な例：レスポンス構築
(defn build-api-response [data {:keys [include-meta? include-debug? user-role]}]
  (cond-> {:data data}
    include-meta?                    (assoc ,,, :meta {:timestamp (System/currentTimeMillis)})
    include-debug?                   (assoc ,,, :debug {:query-time "15ms"})
    (= user-role :admin)            (assoc ,,, :admin-info {:server "prod-1"})
    (and include-meta? include-debug?) (assoc-in ,,, [:meta :debug-enabled] true)))

(build-api-response 
  {:users ["太郎" "花子"]} 
  {:include-meta? true :include-debug? true :user-role :admin})
;; => {:data {:users ["太郎" "花子"]}, 
;;     :meta {:timestamp 1234567890, :debug-enabled true}, 
;;     :debug {:query-time "15ms"}, 
;;     :admin-info {:server "prod-1"}}

;; フォーム検証の例
(defn validate-form [form-data]
  (cond-> {:valid? true :errors []}
    (empty? (:name form-data))        (-> (assoc ,,, :valid? false)
                                          (update ,,, :errors conj "名前は必須です"))
    (< (count (:email form-data)) 5)  (-> (assoc ,,, :valid? false)
                                          (update ,,, :errors conj "メールアドレスが不正です"))
    (< (:age form-data) 0)           (-> (assoc ,,, :valid? false)
                                          (update ,,, :errors conj "年齢は0以上である必要があります"))))

(validate-form {:name "" :email "a@b" :age -1})
;; => {:valid? false, :errors ["名前は必須です" "メールアドレスが不正です" "年齢は0以上である必要があります"]}
```

## 比較表：スレッディングマクロの使い分け

| マクロ | 挿入位置 | 特徴 | 使用場面 |
|--------|----------|------|----------|
| `->` | 最初の引数 | 左から右へ読める | データ構造操作、Java相互運用 |
| `->>` | 最後の引数 | 左から右へ読める | シーケンス操作 |
| `as->` | 任意の位置 | 変数名で参照 | 混合的な操作 |
| `some->` | 最初の引数 | nil安全 | nil の可能性がある操作 |
| `some->>` | 最後の引数 | nil安全 | nil の可能性があるシーケンス操作 |
| `cond->` | 最初の引数 | 条件付き | 条件に応じた変換 |
| `cond->>` | 最後の引数 | 条件付き | 条件に応じたシーケンス操作 |

## ハンズオン練習

以下のコードを実際に試してみましょう：

### 練習1：基本的なデータ変換

```clojure
;; 従来の書き方を->に変換してください
(update (assoc (dissoc {:name "太郎" :age 30 :temp true} :temp) :city "大阪") :age inc)

;; 答え（挿入位置を,,,で示す）
(-> {:name "太郎" :age 30 :temp true}
    (dissoc ,,, :temp)        ; 一時的なフィールドを削除
    (assoc ,,, :city "大阪")   ; 都市を追加
    (update ,,, :age inc))    ; 年齢を1つ増やす
;; => {:name "太郎", :age 31, :city "大阪"}

;; 実行ステップ
;; 1. (dissoc {:name "太郎" :age 30 :temp true} :temp) 
;;    => {:name "太郎", :age 30}
;; 2. (assoc {:name "太郎", :age 30} :city "大阪")
;;    => {:name "太郎", :age 30, :city "大阪"}
;; 3. (update {:name "太郎", :age 30, :city "大阪"} :age inc)
;;    => {:name "太郎", :age 31, :city "大阪"}
```

### 練習2：シーケンス処理

```clojure
;; 1から10までの数字から偶数を取り出し、それぞれを2乗して合計を求める
(->> (range 1 11)
     (filter even? ,,,)       ; 偶数のみフィルタ
     (map #(* % %) ,,,)       ; 各要素を2乗
     (reduce + ,,,))          ; 合計を計算
;; => 220

;; より詳細な実行ステップ
;; 1. (range 1 11) => (1 2 3 4 5 6 7 8 9 10)
;; 2. (filter even? (1 2 3 4 5 6 7 8 9 10)) => (2 4 6 8 10)
;; 3. (map #(* % %) (2 4 6 8 10)) => (4 16 36 64 100)  
;; 4. (reduce + (4 16 36 64 100)) => 220

;; 別の例：文字列リストの処理
(->> ["apple" "banana" "cherry" "date" "elderberry"]
     (filter #(> (count %) 5) ,,,)           ; 5文字以上の単語
     (map clojure.string/upper-case ,,,)     ; 大文字に変換
     (map #(str "FRUIT: " %) ,,,)            ; プレフィックス追加
     (clojure.string/join ", " ,,,))         ; カンマ区切りで結合
;; => "FRUIT: BANANA, FRUIT: CHERRY, FRUIT: ELDERBERRY"
```

### 練習3：条件付き処理

```clojure
;; ユーザーの権限に応じてメニューを構築
(defn build-menu [user]
  (cond-> ["ホーム" "プロフィール"]
    (:admin user)     (conj ,,, "管理画面")
    (:moderator user) (conj ,,, "モデレート")
    (:premium user)   (conj ,,, "プレミアム機能")
    (:developer user) (conj ,,, "開発者ツール")))

(build-menu {:name "田中" :admin true :premium true})
;; => ["ホーム" "プロフィール" "管理画面" "プレミアム機能"]

;; 実行ステップ
;; 初期値: ["ホーム" "プロフィール"]
;; (:admin user) => true → (conj ["ホーム" "プロフィール"] "管理画面") 
;;                      => ["ホーム" "プロフィール" "管理画面"]
;; (:moderator user) => nil (falsy) → スキップ
;; (:premium user) => true → (conj [...] "プレミアム機能")
;;                        => ["ホーム" "プロフィール" "管理画面" "プレミアム機能"]
;; (:developer user) => nil (falsy) → スキップ
```

### 練習4：as->を使った複雑な変換

```clojure
;; JSONライクなデータの複雑な変換
(as-> {:products [{:name "ノートPC" :price 80000 :category "電子機器"}
                  {:name "マウス" :price 2000 :category "電子機器"}
                  {:name "本" :price 1500 :category "書籍"}]} data
  (:products data)                              ; 商品リストを取得
  (group-by :category data)                     ; カテゴリでグループ化
  (update data "電子機器" #(filter (fn [p] (> (:price p) 5000)) %))  ; 高額電子機器のみ
  (mapv (fn [[cat products]]                    ; カテゴリごとの統計作成
          {:category cat 
           :count (count products)
           :avg-price (/ (reduce + (map :price products)) (count products))}) data)
  (sort-by :avg-price data))                    ; 平均価格でソート

;; 実行ステップの詳細解説
;; 1. (:products {...}) => [{:name "ノートPC" ...} {...} {...}]
;; 2. (group-by :category [...]) => {"電子機器" [...] "書籍" [...]}  
;; 3. 電子機器カテゴリから5000円以上の商品のみフィルタ
;; 4. 各カテゴリの統計情報を計算
;; 5. 平均価格でソート
```

### 練習5：some->による安全な操作

```clojure
;; APIレスポンスからの安全なデータ抽出
(defn extract-user-info [api-response]
  (some-> api-response
          (:data ,,,)
          (:user ,,,)
          (:profile ,,,)
          (select-keys ,,, [:name :email :age])
          (update ,,, :name clojure.string/trim)
          (update ,,, :email clojure.string/lower-case)))

;; 成功ケース
(extract-user-info 
  {:data {:user {:profile {:name "  太郎  " :email "TARO@EXAMPLE.COM" :age 30}}}})
;; => {:name "太郎", :email "taro@example.com", :age 30}

;; 失敗ケース（profileが存在しない）
(extract-user-info 
  {:data {:user {}}})
;; => nil

;; 失敗ケース（データ自体が存在しない）  
(extract-user-info {})
;; => nil

;; 従来の書き方（冗長）
(defn extract-user-info-verbose [api-response]
  (when-let [data (:data api-response)]
    (when-let [user (:user data)]
      (when-let [profile (:profile user)]
        (-> profile
            (select-keys [:name :email :age])
            (update :name clojure.string/trim)
            (update :email clojure.string/lower-case))))))
```

### 練習6：混合スレッディングマクロの実用例

```clojure
;; eコマースサイトの商品フィルタリング機能
(defn filter-products [products {:keys [category min-price max-price sort-by desc?]}]
  (cond->> products
    category   (filter #(= (:category %) category) ,,,)      ; カテゴリフィルタ
    min-price  (filter #(>= (:price %) min-price) ,,,)       ; 最低価格フィルタ
    max-price  (filter #(<= (:price %) max-price) ,,,)       ; 最高価格フィルタ
    sort-by    (sort-by sort-by ,,,)                          ; ソート
    desc?      (reverse ,,,)))                                ; 降順にする場合

(def sample-products
  [{:name "ノートPC" :price 80000 :category "電子機器"}
   {:name "マウス" :price 2000 :category "電子機器"}  
   {:name "キーボード" :price 5000 :category "電子機器"}
   {:name "小説" :price 1200 :category "書籍"}
   {:name "技術書" :price 3500 :category "書籍"}])

(filter-products sample-products 
                 {:category "電子機器" 
                  :min-price 3000 
                  :sort-by :price 
                  :desc? true})
;; => ({:name "ノートPC", :price 80000, :category "電子機器"} 
;;     {:name "キーボード", :price 5000, :category "電子機器"})

;; 実行ステップ
;; 1. category "電子機器" でフィルタ => 3件
;; 2. min-price 3000 でフィルタ => 2件（マウスが除外）
;; 3. :price でソート => 価格昇順
;; 4. reverse で降順に => 価格降順
```

### 練習7：パフォーマンス比較

```clojure
;; ネストした関数（読みにくい）
(defn process-nested [data]
  (reduce +
    (map #(* % %)
      (filter odd?
        (map inc
          (range data))))))

;; ->>を使用（読みやすい）
(defn process-threaded [data]
  (->> (range data)
       (map inc ,,,)
       (filter odd? ,,,)
       (map #(* % %) ,,,)  
       (reduce + ,,,)))

;; 両方とも同じ結果
(process-nested 10)   ;; => 165
(process-threaded 10) ;; => 165

;; 実行ステップ（process-threaded 10の場合）
;; 1. (range 10) => (0 1 2 3 4 5 6 7 8 9)
;; 2. (map inc (0 1 2 3 4 5 6 7 8 9)) => (1 2 3 4 5 6 7 8 9 10)
;; 3. (filter odd? (1 2 3 4 5 6 7 8 9 10)) => (1 3 5 7 9)
;; 4. (map #(* % %) (1 3 5 7 9)) => (1 9 25 49 81)
;; 5. (reduce + (1 9 25 49 81)) => 165
```

## 実践的なTips

### 1. 挿入位置の視覚化テクニック

開発中にコードの動作を理解しやすくするため、一時的に`,,,`を使って挿入位置を明示できます：

```clojure
;; デバッグ用の視覚化
(-> user-data
    (assoc ,,, :timestamp (System/currentTimeMillis))  ; どこに挿入されるか明確
    (update ,,, :visits inc)                           ; 前の結果が最初の引数
    (dissoc ,,, :temp-fields))                         ; さらに前の結果が最初の引数
```

### 2. スレッディングマクロの選択フローチャート

```
データ変換が必要？
├─ Yes
│  ├─ 常に最初の引数に挿入？
│  │  ├─ Yes
│  │  │  ├─ nil安全が必要？
│  │  │  │  ├─ Yes → some->
│  │  │  │  └─ No
│  │  │  │     ├─ 条件付き処理？
│  │  │  │     │  ├─ Yes → cond->
│  │  │  │     │  └─ No → ->
│  │  │  └─ No
│  │  │     ├─ 常に最後の引数に挿入？
│  │  │     │  ├─ Yes
│  │  │     │  │  ├─ nil安全が必要？
│  │  │     │  │  │  ├─ Yes → some->>
│  │  │     │  │  │  └─ No
│  │  │     │  │  │     ├─ 条件付き処理？
│  │  │     │  │  │     │  ├─ Yes → cond->>
│  │  │     │  │  │     │  └─ No → ->>
│  │  │     │  └─ No → as->（任意の位置）
└─ No → 通常の関数呼び出し
```

### 3. 読みやすさのベストプラクティス

```clojure
;; 良い例：適切なインデント
(-> user
    (assoc :created-at (now))
    (update :age inc)
    (dissoc :temp))

;; 悪い例：すべて1行
(-> user (assoc :created-at (now)) (update :age inc) (dissoc :temp))

;; 良い例：複雑な処理は分割
(->> data
     (filter valid?)
     (map transform)
     (group-by :category)
     (merge-with concat))

;; 良い例：コメントで各ステップを説明
(->> raw-data
     (map parse-record)        ; JSON文字列をパース
     (filter valid-record?)    ; 不正なレコードを除外  
     (group-by :date)          ; 日付でグループ化
     (transform-groups))       ; 各グループを集計
```

スレッディングマクロを使用することで、コードの可読性が大幅に向上し、データ変換のパイプラインを自然な順序で表現できるようになります。特に`,,,`を使った挿入位置の視覚化は、初学者にとってマクロの動作を理解する上で非常に有効です。