让我们看一下docs的例子:
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中的多态性或MultiModods / Multiple Dispatch,FP中的协议,重点是让代码简短,简洁,让程序员记忆从记忆方法的来源中解放出来。
所以,在OOP中,它看起来像下面的代码:
list.map(square)
在FP multi方法中,它看起来像
map(list, square)
在这两种情况下,编译器/解释器都使用参数类型来确定它应该使用的map
方法。
为什么Elixir不使用相同的方法?为什么需要编写详细的代码并负责决定函数的来源于程序员的肩膀?
有时候不使用多方法并明确指定它是有意义的,比如HttpServer.start(80)
。但对于each
,get
,set
,size
等常规方法,似乎在没有明确指定来源的情况下使用它会更容易。
P.S。
似乎实际上可以使用Elixir中的Protocols来实现。我想知道 - 为什么不使用它?我在GitHub上看到的Elixir项目中的所有代码都使用了ModuleName.fnName
等冗长的详细符号。不知道为什么会这样。协议的使用是否不鼓励,或者太复杂,无法在日常任务中使用?
答案 0 :(得分:11)
您可以以可扩展的方式使用具有不同参数的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
模块。明确导入它会好得多,所以任何阅读代码的人都可以更好地了解使用函数的来源。
换句话说,多个调度和协议仍然不会改变代码始终存在于模块内部的事实,并且除非导入,否则调用始终是合格的。
答案 1 :(得分:2)
在Elixir(和Erlang)中,函数始终存在于模块中。你没有模块就没有功能。甚至你正在呼唤的功能"裸露"在一个模块中 - 它被称为Kernel
(在Erlang中它们位于:erlang
模块中)。
Erlang / Elixir中的函数由模块,名称和arity标识 - 只有这三个元素的组合可以告诉您函数的真实身份,但所有这三个元素仅涉及函数的命名 - 而不是数据它起作用。
相反,协议与命名函数无关 - 它们与处理多态数据有关。所有Enum
函数都由Enumerable
协议支持 - 它们可以使用列表,范围,映射,集等。因此Enum.map
是一个多态函数,但它被称为Enum.map
而不只是map
。如果你对模块名称感到困扰,你可以随时导入你想要使用的模块"裸"。
答案 2 :(得分:0)
所以,在OOP中,它看起来像下面的代码:
list.map(square)
在FP multi方法中,它看起来像
地图(列表,方块)
在OOP中,您获取一个对象(您的列表)并调用此对象正在实现的方法,在这种情况下为map
在Elixir中,您可以使用您的集合和要应用的功能调用存储在Enum模块中的地图功能。 在文档等方面,平面命名空间并不是那么好。通过引用Enum下的所有可枚举相关函数,您的文档更有意义(但这不是唯一的原因)。
答案 3 :(得分:0)
我非常是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