在Elixir中用with的else分支处理正常路径是否符合惯用写法?
问题
我们的代码库中有现成的类型转换函数,用于将值转为特定数据类型。我现在需要把一个未知类型的输入值,转换为数字、NaiveDateTime 或字符串三者之一。现有转换函数的返回格式是{:ok, new_value}或{:error, old_value}元组。目前我用了这样的with逻辑:
with {:error, value} <- coerce_to_number(value), {:error, value} <- coerce_to_naive_datetime(value), {:error, value} <- coerce_to_string(value) do :error else {:ok, value} -> {:ok, value} end
我自己习惯这种写法,也觉得with能清晰表达转换步骤,但同事不认可用else分支处理正常流程的写法。有没有更符合Elixir惯用风格的实现方式?
解决方案
方案1:用Enum.find_value遍历转换函数
把所有转换函数放进列表,依次尝试调用,直到找到第一个返回{:ok, value}的结果,这种写法贴合函数式遍历的风格,逻辑直观易懂:
def coerce_value(value) do [&coerce_to_number/1, &coerce_to_naive_datetime/1, &coerce_to_string/1] |> Enum.find_value(:error, fn coercer -> case coercer.(value) do {:ok, val} -> {:ok, val} {:error, _} -> nil end end) end
Enum.find_value会逐个执行列表里的转换函数,只要某个函数返回非nil的值就直接返回该结果;如果所有函数都返回错误,就返回默认值:error。
方案2:自定义函数+模式匹配
通过多子句函数的模式匹配来依次尝试转换,这种写法完全贴合Elixir的核心特性,结构清晰,可读性强:
def coerce_value(value) do case coerce_to_number(value) do {:ok, val} -> {:ok, val} {:error, val} -> try_coerce_naive_datetime(val) end end defp try_coerce_naive_datetime(value) do case coerce_to_naive_datetime(value) do {:ok, val} -> {:ok, val} {:error, val} -> try_coerce_string(val) end end defp try_coerce_string(value) do case coerce_to_string(value) do {:ok, val} -> {:ok, val} {:error, _} -> :error end end
把每个转换步骤拆成私有函数,层层递进,完全避免了用with的else分支处理正常流程的问题。
方案3:嵌套with(不推荐)
如果坚持想用with,可以调整为嵌套结构,让正常流程落在do块里,但这种写法嵌套较深,可读性不如前两种:
def coerce_value(value) do with {:ok, val} <- coerce_to_number(value) do {:ok, val} else {:error, val} -> with {:ok, val} <- coerce_to_naive_datetime(val) do {:ok, val} else {:error, val} -> case coerce_to_string(val) do {:ok, val} -> {:ok, val} {:error, _} -> :error end end end end
内容的提问来源于stack exchange,提问作者Ian Mac
相关产品推荐
相关产品推荐

