0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Clojure初学者のための関数型プログラミング入門 - コードの読み方から始める

Last updated at Posted at 2025-06-07

Clojure初学者のための関数型プログラミング入門 - コードの読み方から始めよう

この記事では、Clojureのコードがどのように構成されているか、どの部分が関数で、どの部分が引数なのかを詳しく解説します。関数型プログラミングが初めての方でも、コードが読めるようになることを目標にします。

1. Clojureの基本構文 - すべては括弧から始まる

関数呼び出しの基本ルール

Clojureではすべての処理が括弧で囲まれ最初の要素が関数、残りが引数という規則があります。

;; 基本的な構文
(関数名 引数1 引数2 引数3...)

;; 具体例
(+ 1 2 3)
;; ↑ ↑ ↑ ↑
;; │ │ │ └── 引数3(数値の3)
;; │ │ └──── 引数2(数値の2)  
;; │ └────── 引数1(数値の1)
;; └──────── 関数(足し算の+)
;; 結果: 6

重要: 他の言語と違い、関数名が最初に来ます!

他の言語 Clojure
add(1, 2, 3) (+ 1 2 3)
Math.max(5, 10) (max 5 10)
"hello".length() (count "hello")

練習: 基本的な関数を読んでみよう

;; 例1: 数値の計算
(* 4 5)
;; 読み方: 「*関数に、4と5を引数として渡す」
;; 意味: 4 × 5 = 20

;; 例2: 文字列の長さ
(count "hello")
;; 読み方: 「count関数に、"hello"を引数として渡す」
;; 意味: "hello"の文字数 = 5

;; 例3: 最大値を求める
(max 10 5 8 3)
;; 読み方: 「max関数に、10、5、8、3を引数として渡す」
;; 意味: 最大値 = 10

2. データ構造の理解 - マップとベクター

マップ(Map)- キーと値のペア

;; マップの定義
{:name "太郎" :age 25}
;; ↑      ↑     ↑   ↑
;; │      │     │   └── 値(数値の25)
;; │      │     └────── キー(:age)
;; │      └──────────── 値(文字列の"太郎")
;; └─────────────────── キー(:name)

マップの構成要素:

  • {} : マップを表す括弧
  • :name, :age : キーワード(必ず:で始まる)
  • "太郎", 25 : 値

ベクター(Vector)- 順序のあるリスト

;; ベクターの定義
["りんご" "みかん" "バナナ"]
;; ↑       ↑       ↑
;; │       │       └── インデックス2の要素
;; │       └─────────── インデックス1の要素
;; └─────────────────── インデックス0の要素

ベクターの構成要素:

  • [] : ベクターを表す括弧
  • インデックス : 0から始まる位置番号

3. サンプルデータの完全解析

(def company {:name "ABC Corp"
              :employees [{:name "taro" :dept :engineering}
                          {:name "花子" :dept :sales}]})

この1行を詳しく分解してみましょう

(def company {:name "ABC Corp" :employees [...]})
;; ↑   ↑        ↑
;; │   │        └── 引数2: マップ(会社のデータ)
;; │   └─────────── 引数1: シンボル(変数名)
;; └───────────── 特殊形式: def(変数定義)

シンボル(Symbol)とは何か?

シンボルは、Clojureにおける「名前」を表すデータ型です。変数名や関数名として使われます。

;; シンボルの例
company    ;; これがシンボル(クォートなし)
my-name    ;; これもシンボル
user-data  ;; これもシンボル

;; シンボル vs 文字列の違い
company    ;; シンボル(変数名として使われる)
"company"  ;; 文字列(ただのテキストデータ)

シンボルの特徴:

  • クォート(")で囲まない
  • 変数名や関数名として使用される
  • ハイフン(-)を含むことができる
  • 数字で始めることはできない

defでのシンボルの役割

(def company {:name "ABC Corp" :employees [...]})
;; ↑   ↑        ↑
;; │   │        └── 引数2: マップ(会社のデータ)
;; │   └─────────── 引数1: company(シンボル = 変数名)
;; └───────────── 特殊形式: def(変数定義)

重要: defの最初の引数は必ずシンボルでなければならない!

;; ✅ 正しい(シンボルを使用)
(def my-data {:x 1})

;; ❌ 間違い(文字列を使用)
(def "my-data" {:x 1})  ;; エラー!

;; ❌ 間違い(数字で始まる)
(def 123data {:x 1})    ;; エラー!

シンボルの命名規則

良い例 悪い例 理由
user-name userName Clojureはケバブケース推奨
total-count total_count アンダースコアよりハイフン
api-key 123key 数字で始めてはいけない
is-valid? isValid 真偽値には?をつける慣習

クォート ' の詳細解説

'(クォート)の役割

クォート = 「評価を停止する」記号

;; 通常(評価される)
company
;; => {:name "ABC Corp", :employees [...]}
;; 意味: companyシンボルから値を取得

;; クォート付き(評価されない)
'company
;; => company
;; 意味: companyシンボル自体を返す(値を取得しない)

評価される vs 評価されない

書き方 動作 結果 用途
company シンボルを評価(値を取得) {:name "ABC Corp"...} 変数の値を使いたい時
'company シンボルを評価しない company シンボル自体を扱いたい時

具体的な例で理解する

;; 変数定義
(def my-name "太郎")

;; パターン1: 評価する(通常の使用)
my-name
;; => "太郎"
;; 動作: my-nameから値"太郎"を取得

;; パターン2: 評価しない(クォート使用)
'my-name  
;; => my-name
;; 動作: シンボルmy-name自体を返す

;; パターン3: quote関数(同じ意味)
(quote my-name)
;; => my-name
;; 動作: 'my-nameと完全に同じ

リストでのクォートの威力

;; 通常のリスト(関数として評価される)
(+ 1 2 3)
;; => 6
;; 動作: +関数を実行

;; クォートしたリスト(データとして扱われる)
'(+ 1 2 3)
;; => (+ 1 2 3)
;; 動作: リスト自体を返す(実行しない)

;; より複雑な例
'(def company {:name "ABC"})
;; => (def company {:name "ABC"})
;; 動作: defを実行せず、リスト自体を返す

なぜクォートが必要なのか?

Clojureの評価ルール:

  1. 数値・文字列・キーワード → そのまま
  2. シンボル → 値を取得しようとする
  3. リスト → 関数として実行しようとする
;; 問題例: 存在しない変数
undefined-var
;; => エラー: Unable to resolve symbol: undefined-var

;; 解決: クォートで評価を停止
'undefined-var
;; => undefined-var (エラーにならない)

実用的な使用場面

1. メタプログラミング
;; マクロでコードを生成する時
(defmacro my-def [name value]
  `(def ~name ~value))  ;; バッククォートとアンクォート

;; 関数名をデータとして扱う
(def function-names ['+ '- '* '/])
2. 動的なシンボル操作
;; シンボルから文字列に変換
(name 'company)
;; => "company"

;; 文字列からシンボルに変換
(symbol "company")
;; => company

;; シンボルかどうか判定
(symbol? 'company)
;; => true
(symbol? company)
;; => false(値なので)
3. データ構造でのシンボル保存
;; 関数名のリスト
(def operations ['+ '- '* '/])

;; 変数名のリスト
(def variable-names ['x 'y 'z])

;; 設定データ
(def config {'database-host "localhost"
             'database-port 5432})

クォートの種類

記号 名前 機能
' クォート 評価停止 '(+ 1 2)(+ 1 2)
` バッククォート テンプレート `(+ 1 ~x)
~ アンクォート 評価再開 `(+ 1 ~(+ 2 3))
~@ スプライシング リスト展開 `(list ~@[1 2 3])

デバッグでの活用

;; コードをそのまま表示したい時
(println '(-> company :employees second :name))
;; => (-> company :employees second :name)
;; 動作: コード自体を出力(実行結果ではない)

;; 関数定義を確認
(meta #'company)
;; => {:line 1, :column 1, :file "user.clj", :name company, :ns user}

よくある間違いと対処法

;; ❌ 間違い: クォートのつけ忘れ
(def symbols [x y z])
;; エラー: x, y, zの値を取得しようとする

;; ✅ 正しい: シンボル自体を保存
(def symbols ['x 'y 'z])

;; ❌ 間違い: 不要なクォート
(def result '(+ 1 2))
;; => (+ 1 2) (計算されない)

;; ✅ 正しい: 計算したい場合
(def result (+ 1 2))
;; => 3

マクロでのクォート使用例

;; 条件付きデバッグマクロ
(defmacro debug [expr]
  `(let [result# ~expr]
     (println "Debug:" '~expr "=>" result#)
     result#))

;; 使用例
(debug (+ 1 2 3))
;; 出力: Debug: (+ 1 2 3) => 6
;; 戻り値: 6

まとめ:クォートの使い分け

いつクォートを使うか:

  1. シンボル自体を扱いたい時: 'company
  2. コードをデータとして扱いたい時: '(+ 1 2 3)
  3. メタプログラミング: マクロやコード生成
  4. 設定やメタデータ: シンボルをキーとして使用

覚え方:

  • クォートなし: 「この変数の値をください」
  • クォートあり: 「この名前(シンボル)をそのままください」

defの役割:

  • 特殊形式: def
  • 引数1: company (シンボル = 変数名)
  • 引数2: {:name "ABC Corp" ...} (保存する値)
  • 意味: 「companyというシンボル(名前)で、マップデータを保存する」

シンボルの実際の使用例

;; 様々なシンボルの定義
(def user-count 100)           ;; user-countというシンボル
(def is-active? true)          ;; is-active?というシンボル
(def api-endpoint "localhost") ;; api-endpointというシンボル

;; シンボルを使って値を取得
user-count    ;; => 100
is-active?    ;; => true
api-endpoint  ;; => "localhost"

;; 関数定義でもシンボルを使用
(defn calculate-total [items]  ;; calculate-totalというシンボル
  (reduce + items))

;; 関数呼び出し
calculate-total  ;; => #function[...] (関数オブジェクト)
(calculate-total [1 2 3])  ;; => 6 (関数を実行)

シンボル vs その他のデータ型

;; シンボル(変数名)
my-var

;; 文字列(テキストデータ)
"my-var"

;; キーワード(マップのキーなどに使用)
:my-var

;; 数値
42

;; 実際の使い分け例
(def user-name "太郎")      ;; user-name: シンボル、"太郎": 文字列
(def user {:name "太郎"})   ;; user: シンボル、:name: キーワード、"太郎": 文字列
{:name "ABC Corp"
 :employees [{:name "taro" :dept :engineering}
             {:name "花子" :dept :sales}]}

構造の解説:

マップ全体
├── キー: :name
│   └── 値: "ABC Corp" (文字列)
└── キー: :employees
    └── 値: [...] (ベクター)
        ├── インデックス0: {:name "taro" :dept :engineering} (マップ)
        └── インデックス1: {:name "花子" :dept :sales} (マップ)

4. データアクセスの仕組み

基本的なマップアクセス

(:name company)
;; ↑     ↑
;; │     └── 引数: company(マップが入った変数)
;; └──────── 関数: :name(キーワード)

重要: キーワード(:name)は関数として使える

;; これらは同じ意味
(:name company)
(get company :name)

;; 読み方の比較
;; ↓ 「:name関数に、companyを引数として渡す」
(:name company)
;; ↓ 「get関数に、companyと:nameを引数として渡す」
(get company :name)

なぜキーワードが関数になるの?

Clojureでは、キーワードは「そのキーの値を取得する関数」として動作します。

;; キーワード関数の例
(:name {:name "太郎" :age 25})    ;; => "太郎"
(:age {:name "太郎" :age 25})     ;; => 25
(:height {:name "太郎" :age 25})  ;; => nil(存在しないキー)

5. ベクターアクセス関数の詳細

first, second, lastの仕組み

;; サンプルベクター
["A" "B" "C" "D"]

;; first関数の詳細
(first ["A" "B" "C" "D"])
;; ↑     ↑
;; │     └── 引数: ベクター
;; └──────── 関数: first(最初の要素を取得)
;; 結果: "A"

;; second関数の詳細
(second ["A" "B" "C" "D"])
;; ↑      ↑
;; │      └── 引数: ベクター
;; └───────── 関数: second(2番目の要素を取得)
;; 結果: "B"

;; last関数の詳細
(last ["A" "B" "C" "D"])
;; ↑    ↑
;; │    └── 引数: ベクター
;; └─────── 関数: last(最後の要素を取得)
;; 結果: "D"

より詳しいベクターアクセス

;; nth関数(n番目の要素を取得)
(nth ["A" "B" "C" "D"] 2)
;; ↑   ↑                ↑
;; │   │                └── 引数2: インデックス(2番目)
;; │   └─────────────────── 引数1: ベクター
;; └─────────────────────── 関数: nth
;; 結果: "C"(0から数えて2番目)

;; get関数(安全なアクセス)
(get ["A" "B" "C" "D"] 10)
;; ↑   ↑                ↑
;; │   │                └── 引数2: インデックス(存在しない)
;; │   └─────────────────── 引数1: ベクター
;; └─────────────────────── 関数: get
;; 結果: nil(エラーにならない)

6. スレッディングマクロ->の完全解説

従来の書き方の問題点

;; 2番目の従業員の名前を取得したい
;; 従来の書き方(右から左に読む必要がある)
(:name (second (:employees company)))
;; ↑      ↑       ↑
;; │      │       └── 3番目: :employees関数でcompanyから従業員リストを取得
;; │      └─────────── 2番目: second関数で2番目の要素を取得
;; └────────────────── 1番目: :name関数で名前を取得

読み方が不自然: 実際の処理順序と逆順で書かれている!

->マクロの革命

;; スレッディングマクロ(左から右に読める)
(-> company :employees second :name)
;; ↑  ↑       ↑          ↑      ↑
;; │  │       │          │      └── 4番目: :name関数で名前を取得
;; │  │       │          └─────── 3番目: second関数で2番目の要素を取得
;; │  │       └────────────────── 2番目: :employees関数で従業員リストを取得
;; │  └────────────────────────── 1番目: company(開始データ)
;; └───────────────────────────── マクロ: ->(スレッディング)

読み方: 「companyから始めて、employeesを取得し、その2番目を取得し、その名前を取得する」

->マクロの展開過程

->マクロは、実際には以下のように段階的に変換されます:

;; 元のコード
(-> company :employees second :name)

;; ステップ1: companyを:employeesの引数にする
(-> (:employees company) second :name)

;; ステップ2: (:employees company)をsecondの引数にする
(-> (second (:employees company)) :name)

;; ステップ3: (second (:employees company))を:nameの引数にする
(:name (second (:employees company)))

結果: 最終的に従来の書き方と同じになる!

7. 実際の実行過程を追ってみよう

データの準備

;; まずデータを定義
(def company {:name "ABC Corp"
              :employees [{:name "taro" :dept :engineering}
                          {:name "花子" :dept :sales}]})

ステップバイステップ実行

;; ステップ1: 元データ
company
;; => {:name "ABC Corp", :employees [{:name "taro", :dept :engineering} {:name "花子", :dept :sales}]}

;; ステップ2: :employees関数を適用
(:employees company)
;; ↑          ↑
;; │          └── 引数: company(マップ)
;; └───────────── 関数: :employees(キーワード)
;; => [{:name "taro", :dept :engineering} {:name "花子", :dept :sales}]

;; ステップ3: second関数を適用
(second [{:name "taro", :dept :engineering} {:name "花子", :dept :sales}])
;; ↑      ↑
;; │      └── 引数: ベクター(従業員リスト)
;; └───────── 関数: second
;; => {:name "花子", :dept :sales}

;; ステップ4: :name関数を適用
(:name {:name "花子", :dept :sales})
;; ↑     ↑
;; │     └── 引数: マップ(花子のデータ)
;; └──────── 関数: :name(キーワード)
;; => "花子"

->を使った場合の実行

;; 一度に実行
(-> company :employees second :name)
;; => "花子"

;; 途中経過も確認できる
(-> company :employees)                    ;; => [{...} {...}]
(-> company :employees second)             ;; => {:name "花子", :dept :sales}
(-> company :employees second :name)       ;; => "花子"

8. より複雑な例で理解を深める

詳細なデータ構造

(def detailed-company 
  {:name "ABC Corp"
   :founded 2020
   :employees [{:name "taro" 
                :dept :engineering 
                :skills ["Java" "Clojure"]
                :contact {:email "taro@abc.com" :phone "090-1234-5678"}}
               {:name "花子" 
                :dept :sales 
                :skills ["営業" "マーケティング"]
                :contact {:email "hanako@abc.com" :phone "090-8765-4321"}}]})

深いネストへのアクセス解析

;; 最初の従業員のメールアドレスを取得
(-> detailed-company :employees first :contact :email)

;; 完全分解
;; ステップ1
detailed-company
;; => {:name "ABC Corp", :founded 2020, :employees [...]}

;; ステップ2: :employees関数適用
(-> detailed-company :employees)
;; 内部的には (:employees detailed-company) と同じ
;; => [{:name "taro", :dept :engineering, ...} {...}]

;; ステップ3: first関数適用
(-> detailed-company :employees first)
;; 内部的には (first (:employees detailed-company)) と同じ
;; => {:name "taro", :dept :engineering, :skills [...], :contact {...}}

;; ステップ4: :contact関数適用
(-> detailed-company :employees first :contact)
;; 内部的には (:contact (first (:employees detailed-company))) と同じ
;; => {:email "taro@abc.com", :phone "090-1234-5678"}

;; ステップ5: :email関数適用
(-> detailed-company :employees first :contact :email)
;; 内部的には (:email (:contact (first (:employees detailed-company)))) と同じ
;; => "taro@abc.com"

9. 関数の引数が複数ある場合

nth関数を->で使う

;; 3番目の従業員を取得したい場合
;; 従来の書き方
(nth (:employees company) 2)
;; ↑   ↑                  ↑
;; │   │                  └── 引数2: インデックス
;; │   └───────────────────── 引数1: ベクター
;; └─────────────────────── 関数: nth

;; ->を使った書き方
(-> company :employees (nth 2))
;; ↑          ↑         ↑    ↑
;; │          │         │    └── 引数2: インデックス
;; │          │         └─────── 関数: nth
;; │          └─────────────── 1番目の処理: :employees
;; └────────────────────────── データの出発点: company

重要: 引数が複数ある関数は括弧で囲む!

括弧の使い分け

;; 引数が1つの場合(括弧不要)
(-> data :key func)

;; 引数が複数の場合(括弧必要)
(-> data :key (func arg1 arg2))

;; 具体例
(-> company :employees (nth 1))              ;; nth関数は引数2つ
(-> company :employees (get 1))              ;; get関数は引数2つ  
(-> company :employees count)                ;; count関数は引数1つ
(-> company :employees (take 2))             ;; take関数は引数2つ

補足: defの正体と役割

defは関数ではなく「特殊形式(Special Form)」

(def company {:name "ABC Corp" :employees [...]})
;; ↑   ↑       ↑
;; │   │       └── 引数2: 保存する値
;; │   └─────────── 引数1: 変数名(シンボル)
;; └───────────── 特殊形式: def

重要: defは関数ではありません!**特殊形式(Special Form)**と呼ばれる、Clojureの基本的な構文要素です。

関数 vs 特殊形式の違い

種類 評価のタイミング
関数 引数を先に評価してから関数を実行 (+ 1 2) → まず1と2を評価 → +を実行
特殊形式 引数を評価せずに直接処理 (def x 10) → xを評価せず、そのまま変数名として使用

defの具体的な動作

;; defの基本構文
(def 変数名 )

;; 具体例1: 数値の保存
(def my-number 42)
;; 動作: 「my-number」という名前で「42」という値を保存

;; 具体例2: 文字列の保存
(def my-name "太郎")
;; 動作: 「my-name」という名前で「"太郎"」という文字列を保存

;; 具体例3: マップの保存
(def person {:name "花子" :age 30})
;; 動作: 「person」という名前で「{:name "花子" :age 30}」というマップを保存

なぜdefは特殊形式なのか?

;; もしdefが普通の関数だったら...(これは実際には動かない例)
(def company {:name "ABC"})

;; 普通の関数なら、引数が先に評価される
;; 1. company → "companyという変数の値を取得しようとする" → エラー!
;; 2. {:name "ABC"} → マップを評価
;; 3. def関数を実行

;; でも実際のdefは特殊形式なので
;; 1. companyを「変数名」として認識(評価しない)
;; 2. {:name "ABC"}を評価
;; 3. companyという名前で値を保存

Clojureの特殊形式一覧

Clojureにはdef以外にも特殊形式があります:

特殊形式 役割
def 変数定義 (def x 10)
if 条件分岐 (if true "yes" "no")
let ローカル変数 (let [x 5] (+ x 2))
fn 関数定義 (fn [x] (* x 2))
quote 評価停止 (quote (+ 1 2))(+ 1 2)

defの実際の動作を確認

;; ステップ1: 変数定義前
company
;; => エラー: Unable to resolve symbol: company

;; ステップ2: defで変数を定義
(def company {:name "ABC Corp"})
;; => #'user/company (変数が作成されたことを示す)

;; ステップ3: 変数定義後
company
;; => {:name "ABC Corp"} (値が取得できる)

;; ステップ4: 変数の存在確認
(bound? #'company)
;; => true (companyという変数が存在する)

defと関数の組み合わせ

;; 関数の結果をdefで保存
(def sum-result (+ 1 2 3))
;; 動作:
;; 1. (+ 1 2 3) を評価 → 6
;; 2. sum-resultという名前で6を保存

;; 確認
sum-result
;; => 6

;; 他の例
(def employee-count (count (:employees company)))
;; 動作:
;; 1. (:employees company) を評価 → ベクター
;; 2. (count ベクター) を評価 → 数値
;; 3. employee-countという名前で数値を保存

defnの正体

実はdefn(関数定義)もdefを使って作られています!

;; この書き方
(defn greet [name]
  (str "こんにちは、" name "さん!"))

;; は実際にはこれと同じ
(def greet 
  (fn [name]
    (str "こんにちは、" name "さん!")))

;; つまり
;; defn = def + fn の組み合わせ

メモリでの動作イメージ

;; defが実行されるとメモリ上で...

(def company {:name "ABC Corp"})
;; メモリ上:
;; company → {:name "ABC Corp"}

(def person {:name "太郎"})
;; メモリ上:
;; company → {:name "ABC Corp"}
;; person  → {:name "太郎"}

;; 変数を使用すると
(:name company)
;; 動作:
;; 1. companyという名前からメモリ上の値を取得
;; 2. {:name "ABC Corp"} を取得
;; 3. :name関数で "ABC Corp" を取得

def使用時の注意点

;; ❌ 同じ名前で再定義(混乱の原因)
(def x 10)
(def x 20)
x ;; => 20 (前の値は消える)

;; ❌ letの中でdef(推奨されない)
(let [y 5]
  (def z y))  ;; グローバル変数になってしまう

;; ✅ 適切な使い方
(def config {:db-host "localhost" :db-port 5432})
(def users [{:name "太郎"} {:name "花子"}])

まとめ:defの特徴

  1. 特殊形式: 関数ではなく、Clojureの基本構文
  2. 変数作成: グローバルな変数を作成する
  3. 評価制御: 最初の引数(変数名)は評価しない
  4. 永続的: 一度定義すると、そのREPLセッション中はずっと使える
  5. 上書き可能: 同じ名前で再定義できる(注意が必要)

覚え方:

  • def = 「このデータに名前をつけて保存する」命令
  • 関数 = 「データを受け取って何かを返す」処理
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?