导入
实现一个 if 语句
学习 Elixir 中 'if' 语句的实现。
引言
你是否曾感到沮丧,因为某种语言缺少某个恰好适合你正在编写的代码的功能?或者你是否发现自己在重复一些难以拆分为函数的代码片段?又或者,你只是希望能够更贴近问题领域来编写程序?
如果是这样,那么你一定会喜欢本章内容。
但在深入细节之前,请注意一个警告:宏很容易让代码变得难以理解,因为我们实际上是在重写语言的部分功能。因此,只要能用函数实现,我们就绝不使用宏。让我们再次强调:
警告:只要能用函数,就绝不用宏。
事实上,我们通常不会在普通应用代码中编写宏。但如果你正在编写一个库,并希望使用某些元编程技术,那么了解宏的工作原理就至关重要。
if 语句
假设 Elixir 没有 if 语句,只有 case。虽然我们可以接受放弃 while 循环,但没有 if 语句实在难以忍受。于是,我们决定自己实现一个。
我们希望像这样调用它:
1myif «条件» do 2 «条件为真时执行» 3else 4 «条件为假时执行» 5end
我们知道,在 Elixir 中,代码块会被转换为关键字参数,因此这等价于:
1myif «条件», 2 do: «条件为真时执行», 3 else: «条件为假时执行»
一个示例调用如下:
1My.myif 1==2, do: (IO.puts "1 == 2"), else: (IO.puts "1 != 2")
我们尝试将 myif 实现为一个函数:
1defmodule My do 2 def myif(condition, clauses) do 3 do_clause = Keyword.get(clauses, :do, nil) 4 else_clause = Keyword.get(clauses, :else, nil) 5 case condition do 6 val when val in [false, nil] 7 -> else_clause 8 _otherwise 9 -> do_clause 10 end 11 end 12end
我们可以用以下命令来测试运行:
1iex> c "My.exs" 2[My] 3iex> My.myif 1==2, do: (IO.puts "1 == 2"), else: (IO.puts "1 != 2") 41 == 2 51 != 2 6:ok
输出结果可能令人意外。
当我们调用 myif 函数时,Elixir 必须在传入参数前先计算所有参数的值。因此,do: 和 else: 两个分支都会被立即执行,所以我们看到了它们的输出。由于 IO.puts 成功时返回 :ok,实际传递给 myif 的是:
1myif 1==2, do: :ok, else: :ok
这正是最终返回 :ok 的原因。
我们需要一种方法来延迟执行这些代码块。这时,宏就派上用场了。但在实现我们的 myif 宏之前,需要先了解一些背景知识。