能否用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
相关产品推荐
相关产品推荐

