Elixir关键字列表 #

一、关键字列表基础 #

1.1 定义 #

关键字列表是两元素元组的列表,第一个元素必须是原子:

elixir
iex(1)> [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]

iex(2)> [{:a, 1}, {:b, 2}, {:c, 3}]
[a: 1, b: 2, c: 3]

1.2 特性 #

  • 本质是列表
  • 键必须是原子
  • 键可以重复
  • 保持插入顺序
elixir
iex(1)> [a: 1, a: 2, a: 3]
[a: 1, a: 2, a: 3]

iex(2)> is_list([a: 1, b: 2])
true

1.3 空关键字列表 #

elixir
iex(1)> []
[]

二、访问与操作 #

2.1 访问值 #

elixir
iex(1)> kw = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]

iex(2)> kw[:a]
1

iex(3)> kw[:unknown]
nil

iex(4)> Keyword.get(kw, :a)
1

iex(5)> Keyword.get(kw, :unknown, "default")
"default"

2.2 重复键 #

elixir
iex(1)> kw = [a: 1, a: 2, a: 3]
[a: 1, a: 2, a: 3]

iex(2)> kw[:a]
1

iex(3)> Keyword.get_values(kw, :a)
[1, 2, 3]

2.3 添加键值 #

elixir
iex(1)> kw = [a: 1, b: 2]
[a: 1, b: 2]

iex(2)> Keyword.put(kw, :c, 3)
[a: 1, b: 2, c: 3]

iex(3)> Keyword.put_new(kw, :a, 10)
[a: 1, b: 2]

iex(4)> kw ++ [c: 3]
[a: 1, b: 2, c: 3]

2.4 删除键 #

elixir
iex(1)> kw = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]

iex(2)> Keyword.delete(kw, :b)
[a: 1, c: 3]

iex(3)> Keyword.delete_first(kw, :a)
[a: 2, a: 3]

2.5 更新值 #

elixir
iex(1)> kw = [a: 1, b: 2]
[a: 1, b: 2]

iex(2)> Keyword.put(kw, :a, 10)
[a: 10, b: 2]

三、Keyword模块函数 #

3.1 查询 #

elixir
iex(1)> kw = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]

iex(2)> Keyword.has_key?(kw, :a)
true

iex(3)> Keyword.has_key?(kw, :d)
false

iex(4)> Keyword.keys(kw)
[:a, :b, :c]

iex(5)> Keyword.values(kw)
[1, 2, 3]

iex(6)> Keyword.size(kw)
3

3.2 取值 #

elixir
iex(1)> kw = [a: 1, b: 2]
[a: 1, b: 2]

iex(2)> Keyword.fetch(kw, :a)
{:ok, 1}

iex(3)> Keyword.fetch(kw, :unknown)
:error

iex(4)> Keyword.fetch!(kw, :a)
1

iex(5)> Keyword.pop(kw, :a)
{1, [b: 2]}

iex(6)> Keyword.pop_first(kw, :a)
{1, [a: 2, a: 3]}

3.3 合并 #

elixir
iex(1)> Keyword.merge([a: 1], [b: 2])
[a: 1, b: 2]

iex(2)> Keyword.merge([a: 1, b: 1], [b: 2])
[a: 1, b: 2]

3.4 转换 #

elixir
iex(1)> Keyword.to_list([a: 1, b: 2])
[a: 1, b: 2]

iex(2)> Enum.to_list([a: 1, b: 2])
[a: 1, b: 2]

iex(3)> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}

四、函数选项 #

4.1 基本用法 #

关键字列表常用于函数选项:

elixir
defmodule HTTP do
  def get(url, opts \\ []) do
    timeout = Keyword.get(opts, :timeout, 5000)
    headers = Keyword.get(opts, :headers, [])
    follow_redirects = Keyword.get(opts, :follow_redirects, true)

    IO.puts("GET #{url}")
    IO.puts("Timeout: #{timeout}")
    IO.puts("Headers: #{inspect(headers)}")
    IO.puts("Follow redirects: #{follow_redirects}")
  end
end

HTTP.get("https://example.com", timeout: 10_000, headers: [{"Authorization", "Bearer token"}])

4.2 默认值 #

elixir
defmodule Config do
  def build(opts \\ []) do
    %{
      host: Keyword.get(opts, :host, "localhost"),
      port: Keyword.get(opts, :port, 8080),
      ssl: Keyword.get(opts, :ssl, false),
      timeout: Keyword.get(opts, :timeout, 5000)
    }
  end
end

Config.build(port: 3000, ssl: true)

4.3 选项验证 #

elixir
defmodule Validator do
  def validate_opts(opts, allowed_keys) do
    Enum.each(opts, fn {key, _value} ->
      unless key in allowed_keys do
        raise ArgumentError, "unknown option: #{key}"
      end
    end)

    opts
  end
end

五、模式匹配 #

5.1 列表匹配 #

elixir
iex(1)> [a: a, b: b] = [a: 1, b: 2]
[a: 1, b: 2]

iex(2)> a
1

iex(3)> b
2

5.2 头尾匹配 #

elixir
iex(1)> [head | tail] = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]

iex(2)> head
{:a, 1}

iex(3)> tail
[b: 2, c: 3]

5.3 限制 #

关键字列表的模式匹配有限制,不能像映射那样部分匹配:

elixir
iex(1)> [a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]

六、与映射的比较 #

6.1 语法比较 #

elixir
[a: 1, b: 2]
%{a: 1, b: 2}

6.2 特性比较 #

特性 关键字列表 映射
本质 列表 映射
键类型 仅原子 任意
键重复 允许 不允许
顺序 有序 无序
查找性能 O(n) O(log n)
模式匹配 有限 完整

6.3 选择建议 #

使用关键字列表:

  • 函数选项
  • 需要保持顺序
  • 允许重复键

使用映射:

  • 需要快速查找
  • 键不是原子
  • 需要完整模式匹配

七、Ecto查询示例 #

7.1 查询选项 #

elixir
import Ecto.Query

from u in User,
  where: u.age > 18,
  select: u.name,
  order_by: [desc: u.inserted_at],
  limit: 10

7.2 动态查询 #

elixir
def build_query(base_query, opts) do
  query = base_query

  query = if opts[:order_by] do
    order_by(query, ^opts[:order_by])
  else
    query
  end

  query = if opts[:limit] do
    limit(query, ^opts[:limit])
  else
    query
  end

  query
end

八、Phoenix示例 #

8.1 路由选项 #

elixir
scope "/api", Api do
  pipe_through :api

  resources "/users", UserController, only: [:index, :show, :create]
  resources "/posts", PostController, except: [:delete]
end

8.2 视图选项 #

elixir
def render("user.json", %{user: user}) do
  %{
    id: user.id,
    name: user.name,
    email: user.email
  }
end

九、最佳实践 #

9.1 函数选项模式 #

elixir
defmodule MyModule do
  @default_opts [
    timeout: 5000,
    retries: 3,
    debug: false
  ]

  def do_something(arg, opts \\ []) do
    opts = Keyword.merge(@default_opts, opts)

    timeout = Keyword.fetch!(opts, :timeout)
    retries = Keyword.fetch!(opts, :retries)
    debug = Keyword.fetch!(opts, :debug)

    IO.puts("Timeout: #{timeout}")
    IO.puts("Retries: #{retries}")
    IO.puts("Debug: #{debug}")
  end
end

9.2 选项文档 #

elixir
@doc """
Do something useful.

## Options

  * `:timeout` - timeout in milliseconds (default: 5000)
  * `:retries` - number of retries (default: 3)
  * `:debug` - enable debug mode (default: false)

## Examples

    MyModule.do_something("arg", timeout: 10_000, debug: true)

"""
def do_something(arg, opts \\ [])

十、总结 #

本章学习了:

操作 示例
创建 [a: 1, b: 2]
访问 kw[:key]
添加 Keyword.put(kw, :key, value)
删除 Keyword.delete(kw, :key)
获取所有值 Keyword.get_values(kw, :key)
合并 Keyword.merge(kw1, kw2)

准备好学习MapSet与Range了吗?让我们进入下一章。

最后更新:2026-03-27