You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否用Elixir宏定义单个case子句?语法报错问题咨询

问题:用宏定义Elixir case子句时出现语法错误

我想用宏定义case语句里的部分子句,但遇到了语法错误。启用宏生成的:ones、:twos子句时,:threes处会触发语法错误;但手动写的子句能正常运行。想在保留其他手动子句的前提下,用宏定义单个case子项,请问可行吗?

代码示例

defmodule Yacht do
  @type category ::
          :ones
          | :twos
          | :threes
          | :fours
          | :fives
          | :sixes

  defmacro count_case(dice, symbol, value) do
    quote do
      unquote(symbol) ->
        unquote(value) * Enum.count(unquote(dice), &(&1 == unquote(value)))
    end
  end

  @doc """
  Calculate the score of 5 dice using the given category's scoring method.
  """
  @spec score(category :: category(), dice :: [integer]) :: integer
  def score(category, dice) do
    case category do
      # 取消注释会触发语法错误
      # count_case(dice, :ones, 1)
      # count_case(dice, :twos, 2)

      # 手动编写的子句可正常运行
      :threes ->
        3 * Enum.count(dice, &(&1 == 3))

      :fours ->
        4 * Enum.count(dice, &(&1 == 4))

      :fives ->
        5 * Enum.count(dice, &(&1 == 5))

      :sixes ->
        6 * Enum.count(dice, &(&1 == 6))
    end
  end
end

报错信息

** (SyntaxError) lib/yacht.ex:32: unexpected operator ->. If you want to define multiple clauses, the first expression must use ->. Syntax error before: '-'
    (elixir 1.15.5) lib/kernel/parallel_compiler.ex:377: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8

解决方案

完全可行,问题出在宏的调用方式上。Elixir解析case分支时,要求每个分支是模式 -> 表达式的结构,直接在case块里写宏调用会被当成独立表达式,而宏展开后的symbol -> ...本身不是合法表达式,因此触发语法错误。

修复方法1:用unquote插入宏生成的分支

修改score函数中case块的写法,给宏调用加上unquote,让宏展开后的内容直接成为case的合法分支:

def score(category, dice) do
  case category do
    unquote(count_case(dice, :ones, 1))
    unquote(count_case(dice, :twos, 2))

    :threes ->
      3 * Enum.count(dice, &(&1 == 3))

    :fours ->
      4 * Enum.count(dice, &(&1 == 4))

    :fives ->
      5 * Enum.count(dice, &(&1 == 5))

    :sixes ->
      6 * Enum.count(dice, &(&1 == 6))
  end
end

同时可以优化宏的实现,用bind_quoted避免变量捕获问题:

defmacro count_case(dice, symbol, value) do
  quote bind_quoted: [dice: dice, symbol: symbol, value: value] do
    unquote(symbol) ->
      value * Enum.count(dice, &(&1 == value))
  end
end

修复方法2:用宏生成多分支列表,再批量插入

如果需要生成多个类似分支,可以把宏改成返回分支列表,再用unquote_splicing批量插入case块:

defmacro count_cases(dice) do
  quote bind_quoted: [dice: dice] do
    [
      :ones -> 1 * Enum.count(dice, &(&1 == 1)),
      :twos -> 2 * Enum.count(dice, &(&1 == 2))
    ]
  end
end

def score(category, dice) do
  case category do
    unquote_splicing(count_cases(dice))

    :threes ->
      3 * Enum.count(dice, &(&1 == 3))
    :fours ->
      4 * Enum.count(dice, &(&1 == 4))
    :fives ->
      5 * Enum.count(dice, &(&1 == 5))
    :sixes ->
      6 * Enum.count(dice, &(&1 == 6))
  end
end

两种方法都能保留手动编写的分支,同时用宏生成重复逻辑的case子项。


内容的提问来源于stack exchange,提问作者xavdid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 21:39:58