为什么 Elixir 不使用多方法/协议?
Why Elixir doesn't use Multimethods / Protocols?
让我们看看文档中的示例:
square = fn(x) -> x * x end
list = [1, 2, 3, 4]
Enum.map(list, square)
为什么要显式写Enum.map
?为什么它不使用简洁的符号 map [1, 2, 3, 4], square
?
Elixir 有多个调度和协议,但对我来说它使用起来有点奇怪。
如果您考虑 OOP 中的多态性或多方法/多重分派,FP 中的协议,重点是使代码简短明了,并使程序员免于记住方法的来源。
所以,在 OOP 中它看起来像下面的代码:
list.map(square)
在 FP 多方法中它看起来像
map(list, square)
在这两种情况下,编译器/解释器都使用参数的类型来确定它应该使用什么 map
方法。
为什么 Elixir 不使用相同的方法?为什么需要编写冗长的代码并将决定函数来自何处的责任放在程序员的肩上?
有时不使用 multi 方法并显式指定它是有意义的,例如 HttpServer.start(80)
。但是对于像 each
、get
、set
、size
等一般方法,似乎在没有明确指定它来自哪里的情况下使用它要容易得多。
P.S。
似乎在 Elixir 中使用 Protocols 确实可以做到这一点。我想知道 - 为什么不使用它?我在 GitHub 上看到的 Elixir 项目中的所有代码都使用像 ModuleName.fnName
这样冗长的符号。不知道为什么会这样。是否不鼓励使用协议,或者协议过于复杂以至于无法在日常任务中使用?
您可以以可扩展的方式将 Enum.map
与不同的参数一起使用,因为它是通过协议实现的:
iex> Enum.map [1, 2, 3], fn x -> x * x end
[1, 4, 9]
iex> Enum.map 1..3, fn x -> x * x end
[1, 4, 9]
也可以把Enum.map
写成map
只要导入Enum
模块即可:
iex> import Enum
iex> map [1, 2, 3], fn x -> x * x end
[1, 4, 9]
我们默认不包含 Enum
模块。显式导入它要好得多,这样任何阅读您代码的人都可以更好地了解所使用的函数的来源。
换句话说,多个调度和协议仍然没有改变代码始终存在于模块内部并且调用始终合格的事实,除非导入。
So, in OOP it would look like code below:
list.map(square)
In FP multimethod it would looks like
map(list, square)
在 OOP 中,您获取一个对象(您的列表)并调用该对象正在实现的方法,map
在这种情况下
在 Elixir 中,您调用存储在 Enum 模块中的 map 函数,使用您的集合和您要应用的函数。
当涉及到文档等时,平面命名空间并不是很好。通过引用 Enum 下所有与可枚举相关的函数,您的文档更有意义(但这不是唯一的原因)。
在 Elixir(和 Erlang)中,函数总是存在于模块中。没有模块就没有功能。甚至你调用的函数 "bare" 都在一个模块中——它被称为 Kernel
(在 Erlang 中它们位于 :erlang
模块中)。
Erlang/Elixir 中的函数由模块、名称和元数标识 - 只有这三个元素的组合才能告诉您函数的真实标识,但所有这三个元素都只与函数的命名有关功能 - 不是它作用的数据。
相反,协议与命名函数无关——它们与处理多态数据有关。所有 Enum
函数都由 Enumerable
协议支持 - 它们可以处理列表、范围、映射、集合等。因此 Enum.map
是一个多态函数,但它被称为 Enum.map
而不仅仅是 map
。如果您对模块名称感到困扰,您可以随时导入要使用的模块 "bare".
我非常 Elixir 新手。
结构和协议仅解决第一个参数的多态性对我来说似乎确实有限制。我发现像 Julia、CLOS 和 Clojure 中的多方法/多重分派更强大。
但是,看起来您可以通过 defguard
宏使用多参数守卫而无需太多努力就可以非常接近于多方法。
一个滑稽简单的例子看起来像...
defmodule MyModule do
defguard both_integers(i, j) when is_integer(i) and is_integer(j)
def add(i, j) when both_integers(i, j) do
IO.puts i + j
end
def add(i, j) do
IO.puts "do something completely different"
end
end
让我们看看文档中的示例:
square = fn(x) -> x * x end
list = [1, 2, 3, 4]
Enum.map(list, square)
为什么要显式写Enum.map
?为什么它不使用简洁的符号 map [1, 2, 3, 4], square
?
Elixir 有多个调度和协议,但对我来说它使用起来有点奇怪。
如果您考虑 OOP 中的多态性或多方法/多重分派,FP 中的协议,重点是使代码简短明了,并使程序员免于记住方法的来源。
所以,在 OOP 中它看起来像下面的代码:
list.map(square)
在 FP 多方法中它看起来像
map(list, square)
在这两种情况下,编译器/解释器都使用参数的类型来确定它应该使用什么 map
方法。
为什么 Elixir 不使用相同的方法?为什么需要编写冗长的代码并将决定函数来自何处的责任放在程序员的肩上?
有时不使用 multi 方法并显式指定它是有意义的,例如 HttpServer.start(80)
。但是对于像 each
、get
、set
、size
等一般方法,似乎在没有明确指定它来自哪里的情况下使用它要容易得多。
P.S。
似乎在 Elixir 中使用 Protocols 确实可以做到这一点。我想知道 - 为什么不使用它?我在 GitHub 上看到的 Elixir 项目中的所有代码都使用像 ModuleName.fnName
这样冗长的符号。不知道为什么会这样。是否不鼓励使用协议,或者协议过于复杂以至于无法在日常任务中使用?
您可以以可扩展的方式将 Enum.map
与不同的参数一起使用,因为它是通过协议实现的:
iex> Enum.map [1, 2, 3], fn x -> x * x end
[1, 4, 9]
iex> Enum.map 1..3, fn x -> x * x end
[1, 4, 9]
也可以把Enum.map
写成map
只要导入Enum
模块即可:
iex> import Enum
iex> map [1, 2, 3], fn x -> x * x end
[1, 4, 9]
我们默认不包含 Enum
模块。显式导入它要好得多,这样任何阅读您代码的人都可以更好地了解所使用的函数的来源。
换句话说,多个调度和协议仍然没有改变代码始终存在于模块内部并且调用始终合格的事实,除非导入。
So, in OOP it would look like code below:
list.map(square)
In FP multimethod it would looks like
map(list, square)
在 OOP 中,您获取一个对象(您的列表)并调用该对象正在实现的方法,map
在这种情况下
在 Elixir 中,您调用存储在 Enum 模块中的 map 函数,使用您的集合和您要应用的函数。 当涉及到文档等时,平面命名空间并不是很好。通过引用 Enum 下所有与可枚举相关的函数,您的文档更有意义(但这不是唯一的原因)。
在 Elixir(和 Erlang)中,函数总是存在于模块中。没有模块就没有功能。甚至你调用的函数 "bare" 都在一个模块中——它被称为 Kernel
(在 Erlang 中它们位于 :erlang
模块中)。
Erlang/Elixir 中的函数由模块、名称和元数标识 - 只有这三个元素的组合才能告诉您函数的真实标识,但所有这三个元素都只与函数的命名有关功能 - 不是它作用的数据。
相反,协议与命名函数无关——它们与处理多态数据有关。所有 Enum
函数都由 Enumerable
协议支持 - 它们可以处理列表、范围、映射、集合等。因此 Enum.map
是一个多态函数,但它被称为 Enum.map
而不仅仅是 map
。如果您对模块名称感到困扰,您可以随时导入要使用的模块 "bare".
我非常 Elixir 新手。
结构和协议仅解决第一个参数的多态性对我来说似乎确实有限制。我发现像 Julia、CLOS 和 Clojure 中的多方法/多重分派更强大。
但是,看起来您可以通过 defguard
宏使用多参数守卫而无需太多努力就可以非常接近于多方法。
一个滑稽简单的例子看起来像...
defmodule MyModule do
defguard both_integers(i, j) when is_integer(i) and is_integer(j)
def add(i, j) when both_integers(i, j) do
IO.puts i + j
end
def add(i, j) do
IO.puts "do something completely different"
end
end