1. 基本的な構文構造
リスト記法(前置記法)
(関数名 引数1 引数2 引数3 ...)
視覚的表現:
┌─────────────────────────────┐
│ (+ 1 2 3) │
│ │ │ │ │ │
│ │ └─┴─┴── 引数 │
│ └─────── 関数 │
└─────────────────────────────┘
2. map関数の構文
基本形
(map 関数 コレクション)
例
(map inc [1 2 3 4])
;; => (2 3 4 5)
視覚的な動作:
入力: [1 2 3 4]
│ │ │ │
▼ ▼ ▼ ▼
inc inc inc inc
│ │ │ │
▼ ▼ ▼ ▼
出力: (2 3 4 5)
3. map関数の詳細パターン
パターン1: 単一関数、単一コレクション
(map #(* % 2) [1 2 3 4])
;; => (2 4 6 8)
[1 2 3 4] ──┐
│ map
#(* % 2)──┘
│
▼
(2 4 6 8)
パターン2: 複数コレクション
(map + [1 2 3] [4 5 6])
;; => (5 7 9)
[1 2 3] ──┐
│ map +
[4 5 6] ──┘
│
▼
(5 7 9)
詳細:
1+4=5, 2+5=7, 3+6=9
パターン3: 無名関数の使用
(map (fn [x] (* x x)) [1 2 3 4])
;; => (1 4 9 16)
4. 関連する高階関数
filter - 条件に合う要素を抽出
(filter even? [1 2 3 4 5 6])
;; => (2 4 6)
[1 2 3 4 5 6]
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
even? チェック
× ○ × ○ × ○
│ │ │
▼ ▼ ▼
(2 4 6)
reduce - コレクションを単一の値に集約
(reduce + [1 2 3 4])
;; => 10
5. 実用的な例
データ変換の例
;; 人のデータを変換
(def people [{:name "田中" :age 25}
{:name "佐藤" :age 30}
{:name "鈴木" :age 35}])
;; 名前だけを抽出
(map :name people)
;; => ("田中" "佐藤" "鈴木")
;; 年齢を5歳増やす
(map #(update % :age + 5) people)
;; => ({:name "田中", :age 30}
;; {:name "佐藤", :age 35}
;; {:name "鈴木", :age 40})
チェーン処理の例
(->> [1 2 3 4 5 6 7 8 9 10]
(filter odd?) ;; 奇数のみ
(map #(* % %)) ;; 二乗
(reduce +)) ;; 合計
;; => 165
視覚的な流れ:
[1 2 3 4 5 6 7 8 9 10]
│
filter odd?
│
[1 3 5 7 9]
│
map #(* % %)
│
[1 9 25 49 81]
│
reduce +
│
165
6. 無名関数の記法
3つの記法
;; 1. fn記法
(map (fn [x] (* x 2)) [1 2 3])
;; 2. ショートハンド記法
(map #(* % 2) [1 2 3])
;; 3. 名前付き関数
(defn double [x] (* x 2))
(map double [1 2 3])
ショートハンド記法の詳細
#(...) ;; 無名関数の開始
% ;; 第1引数
%1 ;; 第1引数(明示的)
%2 ;; 第2引数
%& ;; 残りの引数(可変長)
7. 実践的な使用パターン
パターン1: データクリーニング
(->> [" hello " " world " " clojure "]
(map clojure.string/trim)
(map clojure.string/upper-case))
;; => ("HELLO" "WORLD" "CLOJURE")
パターン2: ネストしたデータの処理
(def orders [{:items [{:name "商品A" :price 100}
{:name "商品B" :price 200}]}
{:items [{:name "商品C" :price 150}]}])
;; 各注文の合計金額を計算
(map (fn [order]
(->> (:items order)
(map :price)
(reduce +)))
orders)
;; => (300 150)
8. パフォーマンスと遅延評価
遅延シーケンス
;; mapは遅延評価される
(def lazy-seq (map println [1 2 3 4 5]))
;; この時点では何も出力されない
;; 実際に値が必要になったときに評価
(doall lazy-seq)
;; この時点で1 2 3 4 5が出力される
無限シーケンス
;; 無限の数列
(def fibonacci-seq
(map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
;; 最初の10個を取得
(take 10 fibonacci-seq)
;; => (0 1 1 2 3 5 8 13 21 34)
9. mapの返り値の型について
重要なポイント:mapは常に遅延シーケンスを返す
;; 入力がベクターでも
(map inc [1 2 3])
;; => (2 3 4) ← これは遅延シーケンス(lazy sequence)
;; 入力がリストでも
(map inc '(1 2 3))
;; => (2 3 4) ← これも遅延シーケンス
;; 型を確認
(type (map inc [1 2 3]))
;; => clojure.lang.LazySeq
視覚的な比較
入力の型 │ map処理 │ 出力の型
─────────────────────────────────────
[1 2 3] │ map │ LazySeq
ベクター │ inc │ (2 3 4)
│ │
'(1 2 3) │ map │ LazySeq
リスト │ inc │ (2 3 4)
│ │
#{1 2 3} │ map │ LazySeq
セット │ inc │ (2 3 4)
10. Clojureのコレクション型の違い
基本的な4つのコレクション型
1. ベクター(Vector)[]
[1 2 3 4]
特徴:
-
インデックスアクセスが高速
O(1) - 末尾への追加が高速
- 順序あり
- 重複あり
視覚的構造:
インデックス: 0 1 2 3
ベクター: [1][2][3][4]
↑ ↑ ↑ ↑
高速アクセス可能
2. リスト(List)'()
'(1 2 3 4)
特徴:
-
先頭への追加が高速
O(1) -
先頭アクセスが高速、末尾は遅い
O(n) - 順序あり
- 重複あり
視覚的構造:
'(1 2 3 4)
↓ ↓ ↓ ↓
1→2→3→4→nil
↑
先頭アクセス高速
3. セット(Set)#{}
#{1 2 3 4}
特徴:
- 重複なし
- 順序なし(ハッシュセット)
-
メンバーシップテストが高速
O(1)
視覚的構造:
#{1 2 3 4}
┌─────────────┐
│ 3 1 │ ← 順序は保証されない
│ 4 2 │
└─────────────┘
4. マップ(Map){}
{:a 1 :b 2 :c 3}
特徴:
- キー・値のペア
-
キーによる高速検索
O(1) - 順序なし(ハッシュマップ)
視覚的構造:
{:a 1, :b 2, :c 3}
↓ ↓ ↓
:a ──→ 1
:b ──→ 2
:c ──→ 3
5. 遅延シーケンス(Lazy Sequence)
(map inc [1 2 3]) ; これが遅延シーケンス
特徴:
- 必要時に計算される
- 無限シーケンスを扱える
- メモリ効率が良い
11. 型変換の方法
遅延シーケンスから他の型への変換
(def lazy-result (map inc [1 2 3]))
;; ベクターに変換
(vec lazy-result)
;; => [2 3 4]
;; リストに変換(実際は既にシーケンス)
(seq lazy-result)
;; => (2 3 4)
;; セットに変換
(set lazy-result)
;; => #{2 3 4}
;; 強制評価(型は変わらない)
(doall lazy-result)
;; => (2 3 4)
視覚的変換フロー
map処理
↓
LazySeq (2 3 4)
├─ vec ──→ [2 3 4] ベクター
├─ set ──→ #{2 3 4} セット
└─ doall → (2 3 4) 評価済みシーケンス
12. パフォーマンスの比較
アクセス速度比較表
操作 │ ベクター │ リスト │ セット │
─────────────────────────────────────────
先頭アクセス │ O(1) │ O(1) │ N/A │
末尾アクセス │ O(1) │ O(n) │ N/A │
インデックス │ O(1) │ O(n) │ N/A │
先頭追加 │ O(1) │ O(1) │ N/A │
末尾追加 │ O(1) │ O(n) │ N/A │
メンバー検索 │ O(n) │ O(n) │ O(1) │
13. 実用的な使い分け
使用場面別推奨
ベクター [] を使う場面
;; インデックスアクセスが必要
(get [10 20 30 40] 2) ; => 30
;; 末尾への追加が多い
(conj [1 2 3] 4) ; => [1 2 3 4]
リスト '() を使う場面
;; 先頭への追加が多い
(conj '(2 3 4) 1) ; => (1 2 3 4)
;; 関数の引数リストやマクロ
'(defn my-func [x] (+ x 1))
セット #{} を使う場面
;; 重複排除
(set [1 2 2 3 3 4]) ; => #{1 2 3 4}
;; メンバーシップテスト
(#{:admin :user :guest} :admin) ; => :admin (truthy)
14. LazySeq(遅延シーケンス)とリストの違い
重要:LazySeqとリストは別物
;; リスト(List)
(def my-list '(1 2 3 4))
(type my-list)
;; => clojure.lang.PersistentList
;; 遅延シーケンス(LazySeq)
(def my-lazy (map inc [1 2 3]))
(type my-lazy)
;; => clojure.lang.LazySeq
;; 見た目は同じでも...
my-list ; => (1 2 3 4)
my-lazy ; => (2 3 4)
視覚的な違い
リスト '(1 2 3 4) 遅延シーケンス (map inc [1 2 3])
┌─────────────────┐ ┌─────────────────────────────┐
│ [1]→[2]→[3]→[4] │ │ 未計算 → 未計算 → 未計算 │
│ ↑ │ │ ↓ ↓ ↓ │
│ 即座に全て │ │ 計算 計算 計算 │
│ アクセス可能 │ │ 必要時 必要時 必要時 │
└─────────────────┘ └─────────────────────────────┘
内部構造の違い
リスト(PersistentList)
'(1 2 3 4)
特徴:
- すべての要素が既に存在
- メモリに全て格納済み
- 即座にアクセス可能
- 有限
内部構造:
先頭 → 1 → 2 → 3 → 4 → nil
↑ ↑ ↑ ↑
すべて実体として存在
遅延シーケンス(LazySeq)
(map inc [1 2 3])
特徴:
- 必要になるまで計算されない
- 一度計算されるとキャッシュ
- 無限シーケンスも可能
- メモリ効率が良い
内部構造:
LazySeq
├─ 関数: (fn [] (map inc [1 2 3]))
├─ 状態: 未実現
└─ キャッシュ: nil
↓ 最初のアクセス時
LazySeq
├─ 関数: (fn [] (map inc [1 2 3]))
├─ 状態: 実現済み
└─ キャッシュ: (2 3 4)
実際の動作比較
;; リストの場合
(def my-list '(1 2 3 4))
(println "リスト作成完了") ; すぐに出力される
;; 遅延シーケンスの場合
(def my-lazy (map #(do (println "計算中:" %)
(* % 2))
[1 2 3 4]))
(println "遅延シーケンス作成完了") ; すぐに出力される(まだ計算されていない)
;; 実際にアクセスするまで計算されない
(first my-lazy) ; この時点で「計算中: 1」が出力される
;; => 2
(doall my-lazy) ; この時点で残りの「計算中: 2, 3, 4」が出力される
;; => (2 4 6 8)
パフォーマンスの違い
メモリ使用量
;; 大きなリスト(すべてメモリに保持)
(def big-list (range 1000000)) ; 100万個の数値をメモリに保持
;; 遅延シーケンス(必要な分だけ)
(def big-lazy (map inc (range 1000000))) ; 計算はアクセス時のみ
;; 最初の10個だけ使用
(take 10 big-lazy) ; メモリ使用量は最小限
計算タイミング
;; リスト:作成時にすべて計算
(time (def eager-list (doall (map #(Thread/sleep 1) (range 5)))))
;; => 5秒かかる
;; 遅延シーケンス:アクセス時に計算
(time (def lazy-seq (map #(Thread/sleep 1) (range 5))))
;; => 即座に完了(計算はまだされていない)
(time (doall lazy-seq)) ; この時点で5秒かかる
無限シーケンスの例
;; リストでは不可能(メモリ不足でクラッシュ)
;; (def infinite-list (range)) ; これは危険
;; 遅延シーケンスなら可能
(def infinite-lazy (iterate inc 0)) ; 0, 1, 2, 3, ...
;; 必要な分だけ取得
(take 5 infinite-lazy) ; => (0 1 2 3 4)
実用的な違いの例
ファイル処理での違い
;; リスト方式(危険:大ファイルでメモリ不足)
(defn process-file-eager [filename]
(doall (map process-line (line-seq (reader filename)))))
;; 遅延シーケンス方式(安全:一行ずつ処理)
(defn process-file-lazy [filename]
(map process-line (line-seq (reader filename))))
;; 使用例
(take 10 (process-file-lazy "huge-file.txt")) ; 最初の10行のみ処理
15. いつリストを使い、いつ遅延シーケンスを使うか
リスト '() を使う場面
;; 1. 小さなデータセット
(def colors '(:red :green :blue))
;; 2. 関数の引数(コード as データ)
'(defn my-func [x] (+ x 1))
;; 3. 先頭操作が多い場合
(conj '(2 3 4) 1) ; => (1 2 3 4)
遅延シーケンス(map等の結果)を使う場面
;; 1. 大きなデータセット
(map process-data huge-dataset)
;; 2. 無限シーケンス
(take 100 (iterate inc 0))
;; 3. パイプライン処理
(->> data
(map transform1)
(filter predicate)
(map transform2)
(take 10))
;; 4. メモリ効率が重要
(map expensive-computation large-collection)
変換のタイミング
;; 遅延シーケンス → リスト(通常は不要、seqのまま使える)
(seq lazy-sequence)
;; 遅延シーケンス → ベクター(ランダムアクセスが必要)
(vec lazy-sequence)
;; 遅延シーケンス → 強制評価(副作用がある場合)
(doall lazy-sequence)
16. map使用時の実践的パターン
パターン1: 結果をベクターで欲しい場合
;; 一般的なパターン
(mapv inc [1 2 3 4]) ; => [2 3 4 5] (ベクター)
;; または
(vec (map inc [1 2 3 4])) ; => [2 3 4 5]
パターン2: セットで重複排除したい場合
(set (map #(mod % 3) [1 2 3 4 5 6]))
;; => #{0 1 2}
パターン3: 遅延評価を活用する場合
;; 無限シーケンス
(def fibonacci (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
;; 必要な分だけ取得
(take 10 fibonacci)
;; => (0 1 1 2 3 5 8 13 21 34)
まとめ
mapの返り値について
- mapは常に遅延シーケンスを返す
-
入力の型に関係なく、出力は
LazySeq - 必要に応じて他の型に変換する
コレクション型の選択指針
- インデックスアクセスが必要 → ベクター
- 先頭操作が多い → リスト
- 重複排除・メンバー検索 → セット
- キー・値の関連付け → マップ
- 遅延評価・無限シーケンス → 遅延シーケンス
練習のポイント:
- まずは単純な例から始める
- 無名関数の記法に慣れる
- 他の高階関数と組み合わせる
- 実際のデータで試してみる
- 返り値の型を意識する
- 適切なコレクション型を選択する