利用宏为elixir增加while循环控制结构

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这样的结构还是允许的,用处还是很大的,在很多需要无限循环的地方可以节约很多的代码。

猜你喜欢

转载自blog.csdn.net/u011031257/article/details/80847661