c语言中的宏定义:
#define SUM(a, b) (a + b)
程序中就可以用SUM这个求和函数(相当于符号替换),编译器会在编译时将所有出现SUM(a, b)的地方替换成(a + b), 它是个预编译指令,在编译时执行。其实elixir中的宏也具有类似的功能,它能为elixir带来新的特性。比如说elixir中是没有while循环的,如果我们想在elixir中使用while循环怎么办比如说我想用这样一个功能:
while true do
receive do
"break"-> break
_ -> IO.puts "do something else"
end
end
轮询接受消息,当接收到break的消息时就退出while循环(elixir的循环有很多种,关键在于这个break,我们需要一种帮助我们提前结束循环的能力,又不需要附加很多代码,在需要的时候调用break就能跳出循环这是我们所希望有的,虽然这很不函数式)。接下来就用elixir的宏来实现这一功能!首先我们需要一个无限循环(elixir中不直接提供不过我们可以使用Stream.cycle产生无限流)
for _ <- Stream.cycle([:any]) do
nil
end
有了无限循环我们需要一个宏来定义while
defmodule WhileLoop do
defmacro while(condition_expression, do: do_block) do
quote do
for _ <- Stream.cycle([:any]) do
if unquote(condition_expression) do
unquote(do_block)
else
# break out of loop
end
end
end
end
end
这里就用到了quote和unquote,我们定义一个while宏判断条件condition_expression来决定是否执行do_block中的程序结构。我们如何break一个无限循环呢?throw一个error可不可以?答案是当然可以!
defmodule WhileLoop do
defmacro while(condition_expression, do: do_block) do
quote do
for _ <- Stream.cycle([:any]) do
if unquote(condition_expression) do
unquote(do_block)
else
break()
end
end
end
end
def break, do: throw :break
end
我们增加一个函数break定义,并且在else中调用break这样就能跳出无限循环了,但是好像还有一个问题是不是这样程序就崩掉了!所以还是要添加一个容错try catch,对!
defmodule WhileLoop do
defmacro while(condition_expression, do: do_block) do
quote do
try do
for _ <- Stream.cycle([:any]) do
if unquote(condition_expression) do
unquote(do_block)
else
break()
end
end
catch :break -> :ok
end
end
end
def break, do: throw :break
end
我们把自己throw的break给抓住,这样系统就不会崩掉了。这里quote与unquote的配合是很巧妙的,quote的do end结构中的程序结构都会变成符号描述(AST)而不会执行其程序语句结果(保证了do_block程序结构不会在if判断之前执行,因为while其实是函数condition_expression和逗号后面的都是参数,对比python中的函数带()和不带(),fun和fun()一个会执行一个是函数的引用,你可以传进另一个函数使用),但是我们需要在满足条件时执行do_block程序结构,因此也需要unquote(do_block),condition_expression同理。好了while循环结构也完成了!我们再用开头的程序试一下while循环是否工作正常,来一个完整的程序!
import WhileLoop
{:ok, pid} = Task.start fn ->
while true do
receive do
"break" -> break
_ -> IO.puts "do something else"
end
end
end
Process.send pid, "hi", []
Process.send pid, "break", []
Process.alive? pid
将以上代码粘贴到iex中(要先加载WhileLoop模块可以直接粘贴进iex shell),可以得到如下运行结果
iex(13)> Process.send pid, "hi", []
do something else
:ok
iex(14)> Process.send pid, "hello", []
do something else
:ok
iex(16)> Process.alive? pid
true
iex(17)> Process.send pid, "break", []
:ok
iex(18)> Process.alive? pid
false
在向进程发送hi和hello消息后while循环还是没有退出继续等待接受消息,但发送完break后进程也随之退出说明while循环正常break!看来我们的while如预期般的运行了^^,不过此处还是有一个问题我们并不能在do_block中直接更改condition_expression条件值来达到跳出循环,说白了函数式编程语言是不允许你去改变函数参数的。但是while x < y do something这样的结构还是允许的,用处还是很大的,在很多需要无限循环的地方可以节约很多的代码。