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の`:keys`の戻り値について

Posted at

基本的な概念

:keysはClojureの分割束縛 - destructuringで使用される特別なキーワードです。関数の戻り値そのものではなく、マップから特定のキーの値を抽出するための構文です。

:keysの動作原理

;; 元のマップ
(def person {:name "田中" :age 30 :city "東京"})

;; :keysを使った分割束縛
(let [{:keys [name age]} person]
  (println name age))
;; => 田中 30

視覚的な説明図

マップの構造:
┌─────────────────────────────────┐
│ {:name "田中"                   │
│  :age 30                       │
│  :city "東京"}                 │
└─────────────────────────────────┘
                │
                ▼
        {:keys [name age]}
                │
                ▼
┌─────────────────────────────────┐
│ name → "田中"                   │
│ age  → 30                      │
└─────────────────────────────────┘

使用例とパターン

1. 関数の引数での使用

(defn greet [{:keys [name age]}]
  (str "こんにちは、" name "さん(" age "歳)"))

(greet {:name "佐藤" :age 25})
;; => "こんにちは、佐藤さん(25歳)"

2. let束縛での使用

(let [data {:x 10 :y 20 :z 30}
      {:keys [x y]} data]
  (+ x y))
;; => 30

3. 関数の戻り値を分割束縛

(defn get-coordinates []
  {:x 100 :y 200})

(let [{:keys [x y]} (get-coordinates)]
  (println "座標:" x y))
;; => 座標: 100 200

重要なポイント

  1. :keys自体は値を返さない - 分割束縛の構文
  2. 元のマップから値を抽出 - キー名と同じ変数名を作成
  3. 存在しないキーはnilになる
(let [{:keys [name email]} {:name "山田"}]
  (println name email))
;; => 山田 nil

他の分割束縛オプション

  • :keys - キーワードキー用
  • :strs - 文字列キー用
  • :syms - シンボルキー用
;; 文字列キーの場合
(let [{:strs [name age]} {"name" "鈴木" "age" 35}]
  (println name age))
;; => 鈴木 35

:keys:asの組み合わせ

:keysで値を抽出しつつ、元のマップ全体も保持したい場合は:asを使います。

基本的な使用法

(defn process-user [{:keys [name age] :as params}]
  (println "名前:" name)
  (println "年齢:" age)
  (println "全体のパラメータ:" params))

(process-user {:name "田中" :age 30 :city "東京"})
;; => 名前: 田中
;; => 年齢: 30
;; => 全体のパラメータ: {:name "田中", :age 30, :city "東京"}

視覚的な説明図

元の引数: {:name "田中" :age 30 :city "東京"}
                    │
                    ├─────────────────┐
                    │                 │
                    ▼                 ▼
            :keys で抽出        :as で元のまま保持
                    │                 │
                    ▼                 ▼
┌─────────────────────────────────────────────┐
│ 抽出された個別の値:                         │
│ name → "田中"                               │
│ age  → 30                                  │
│                                             │
│ 元の引数そのまま:                           │
│ params → {:name "田中" :age 30 :city "東京"} │
│          ↑                                 │
│          完全に同じオブジェクト             │
└─────────────────────────────────────────────┘

重要なポイント

:asは分割束縛の結果ではなく、元の引数をそのまま参照します

(defn demo [{:keys [name] :as original}]
  (println "抽出した値:" name)
  (println "元の引数:" original)
  (println "同じオブジェクト?" (identical? original {:name "田中" :age 30 :city "東京"})))

;; 引数として渡されたマップと:asで束縛された値は同一
(let [input-map {:name "田中" :age 30 :city "東京"}]
  (demo input-map))

実用的な例

(defn validate-and-process [{:keys [name email] :as user-data}]
  (cond
    (empty? name) {:error "名前が必要です" :data user-data}
    (empty? email) {:error "メールが必要です" :data user-data}
    :else {:success true :user user-data :greeting (str "こんにちは " name "さん")}))

(validate-and-process {:name "佐藤" :email "sato@example.com" :phone "090-1234-5678"})
;; => {:success true, 
;;     :user {:name "佐藤", :email "sato@example.com", :phone "090-1234-5678"}, 
;;     :greeting "こんにちは 佐藤さん"}

let束縛での使用

(let [config {:host "localhost" :port 3000 :debug true}
      {:keys [host port] :as full-config} config]
  (println "接続先:" host ":" port)
  (when (:debug full-config)
    (println "デバッグモード有効 - 設定:" full-config)))

ネストした分割束縛での使用

(defn process-request [{:keys [user params] :as request}]
  (let [{:keys [name id] :as user-info} user
        {:keys [action data] :as request-params} params]
    {:processed-by name
     :user-id id
     :action action
     :original-request request}))

分割束縛とLazy Sequenceの関係

重要:分割束縛は必要な部分だけlazy sequenceを実現(realize)します

Lazy Sequenceでの分割束縛

;; 無限のlazy sequence
(defn infinite-numbers []
  (iterate inc 0))

;; 分割束縛で最初の3つだけを取得
(let [[a b c] (infinite-numbers)]
  (println a b c))
;; => 0 1 2
;; 無限シーケンスだが、必要な3つだけが計算される

視覚的な説明図

Lazy Sequence: [0 1 2 3 4 5 6 ...∞]
                │ │ │
                ▼ ▼ ▼  (必要な分だけ実現)
        [a b c] の分割束縛
                │
                ▼
┌─────────────────────────────┐
│ a → 0  (実現済み)           │
│ b → 1  (実現済み)           │  
│ c → 2  (実現済み)           │
│                             │
│ 残りは未実現のまま          │
└─────────────────────────────┘

実例での動作確認

;; 副作用のあるlazy sequence(計算のタイミングを確認)
(defn debug-seq []
  (map (fn [x] 
         (println "計算中:" x)
         x) 
       (range 10)))

;; 分割束縛で最初の2つだけ使用
(let [[first second] (debug-seq)]
  (println "結果:" first second))

;; 出力:
;; 計算中: 0
;; 計算中: 1
;; 結果: 0 1
;; → 必要な2つだけが計算された!

マップの分割束縛は即座に実現

;; マップの場合は即座にキーにアクセス
(defn create-map-with-lazy-vals []
  {:a (do (println "aを計算") 1)
   :b (do (println "bを計算") 2)
   :c (delay (do (println "cを計算(遅延)") 3))})

(let [{:keys [a b]} (create-map-with-lazy-vals)]
  (println "使用:" a b))

;; 出力:
;; aを計算
;; bを計算
;; 使用: 1 2
;; → :cは使わないが、マップ作成時に:aと:bは計算される

ネストしたシーケンスでの分割束縛

;; ネストしたlazy sequenceの場合
(defn nested-lazy []
  (map (fn [x] 
         (println "外側計算:" x)
         (map (fn [y] 
                (println "内側計算:" y)
                (* x y))
              (range 5)))
       (range 3)))

;; 最初の要素の最初の2つだけを取得
(let [[[a b] & rest] (nested-lazy)]
  (println "結果:" a b))

;; 出力:
;; 外側計算: 0
;; 内側計算: 0
;; 内側計算: 1
;; 結果: 0 0

パフォーマンスの考慮事項

;; 大きなlazy sequenceでの効率的な分割束縛
(defn process-large-data []
  (->> (range 1000000)
       (map #(* % %))
       (filter even?)))

;; 最初の10個だけを処理
(let [[a b c & more] (take 10 (process-large-data))]
  (println "最初の3つ:" a b c)
  (println "残り:" (count more)))
;; → 100万個全てではなく、必要な分だけが計算される

まとめ

分割束縛は確かにlazy sequenceを必要な分だけ実現します:

  1. シーケンスの分割束縛 → 必要な要素数だけ計算
  2. マップの分割束縛 → キーへの即座アクセス(既に実現済みの値)
  3. ネストした構造 → 必要な深さまで段階的に計算
  4. パフォーマンス → メモリ効率が良い(全体を実現しない)

これがClojureの遅延評価の強力な特徴の一つです。

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?