作为我们Dev Book Club的一部分,我在Elixir中编写了一个随机密码生成器.决定使用元编程,然后用宏来写它来干一些东西.
这非常有效:
# lib/macros.ex defmodule Macros do defmacro define_alphabet(name, chars) do len = String.length(chars) - 1 quote do def unquote(:"choose_#{name}")(chosen, 0) do chosen end def unquote(:"choose_#{name}")(chosen, n) do alphabet = unquote(chars) unquote(:"choose_#{name}")([(alphabet |> String.at :random.uniform(unquote(len))) | chosen], n - 1) end end end end # lib/generate_password.ex defmodule GeneratePassword do require Macros Macros.define_alphabet :alpha, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" Macros.define_alphabet :special, "~`!@#$%^&*?" Macros.define_alphabet :digits, "0123456789" def generate_password(min_length, n_special, n_digits) do [] |> choose_alpha(min_length - n_special - n_digits) |> choose_special(n_special) |> choose_digits(n_digits) |> Enum.shuffle |> Enum.join end end
我想在Dict/map或甚至列表中定义字母表,并迭代它以调用Macros.define_alphabet,而不是手动调用它3次.但是,当我尝试这个时,使用下面的代码,它无法编译,无论我用什么结构来保存字母表.
alphabets = %{ alpha: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", special: "~`!@#$%^&*?", digits: "0123456789", } for {name, chars} <- alphabets, do: Macros.define_alphabet(name, chars)
给出以下错误:
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] Compiled lib/macros.ex == Compilation error on file lib/generate_password.ex == ** (FunctionClauseError) no function clause matching in String.Graphemes.next_grapheme_size/1 (elixir) unicode/unicode.ex:231: String.Graphemes.next_grapheme_size({:chars, [line: 24], nil}) (elixir) unicode/unicode.ex:382: String.Graphemes.length/1 expanding macro: Macros.define_alphabet/2 lib/generate_password.ex:24: GeneratePassword (module) (elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8
我已经尝试将字母表映射为列表列表,元组列表,原子映射 - >字符串和字符串 - >字符串,它似乎并不重要.我也尝试将这些对用于Enum.each而不是使用"for"理解,如下所示:
alphabets |> Enum.each fn {name, chars} -> Macros.define_alphabet(name, chars) end
所有这些都给出了相同的结果.认为它可能与调用:random.uniform有关,并将其更改为:
alphabet |> to_char_list |> Enum.shuffle |> Enum.take(1) |> to_string
这只是稍微改变了错误,:
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] == Compilation error on file lib/generate_password.ex == ** (Protocol.UndefinedError) protocol String.Chars not implemented for {:name, [line: 24], nil} (elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1 (elixir) lib/string/chars.ex:17: String.Chars.to_string/1 expanding macro: Macros.define_alphabet/2 lib/generate_password.ex:24: GeneratePassword (module) (elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8
即使有了这个改变,当我在顶部手动调用Macros.define_alphabet时工作正常,但是当我在任何类型的理解或使用Enum.each时都没有.
这不是什么大不了的事,但我希望能够以编程方式添加到字母表列表中并从中删除,具体取决于用户定义的配置.
我确信随着我进一步进入Metaprogramming Elixir,我将能够解决这个问题,但如果有人有任何建议,我会很感激.