Elixir 功能在什么时候会成为误导?

Tia*_*ões 1 functional-programming elixir pattern-matching

一般来说,函数式编程以更清晰和简洁而自豪。您没有副作用/状态管理的事实使开发人员更容易推理他们的代码并确保行为。这个真理能达到多远?

我仍在学习 Elixir,但给出了 Coding Gnome 的代码:

def make_move(game = %{ game_state: state }, _guess)
  when state in [:won, :lost] do
    ...
end

def make_move(game = %{ game_state: state }, _guess)
  when state in [:pending] do
    ...
end

def make_move(game, guess) do
  ...
end
Run Code Online (Sandbox Code Playgroud)

人们可以在 Javascript 中毫不费力地将其编写为:

def make_move(game = %{ game_state: state }, _guess)
  when state in [:won, :lost] do
    ...
end

def make_move(game = %{ game_state: state }, _guess)
  when state in [:pending] do
    ...
end

def make_move(game, guess) do
  ...
end
Run Code Online (Sandbox Code Playgroud)

不考虑 Elixir 编译器提供的所有类型/结构安全性,Elixir 程序员必须先阅读整个文件,然后才能确保没有具有不同签名的函数劫持另一个函数,对吗?我觉得这会增加程序员的开销,因为这是你必须考虑的另一件事,甚至在查看函数的实现之前。

除此之外,我觉得这是一种误导,因为make_move除非您事先知道所有其他函数和签名类型,否则您无法 100% 确定某个案例最终会在该通用函数中结束,而使用条件,您有更清晰的路径流动。

这可以用更好的方式重写吗?这些抽象在什么时候开始对程序员产生影响?

m3c*_*ers 8

我认为这主要归结为偏好,并且通常带有简单条件的模式匹配的简单练习不会显示“清晰”模式匹配可以提供的范围。但我怀疑是因为我更喜欢模式匹配,无论如何,我会咬牙切齿。

在这种情况下,可以说 switch 更具可读性和直接性,但请注意,没有什么可以阻止您在 Elixir(或 erlang)中编写非常相似的东西

def make_move(game = %{ game_state: state }, _guess) do
    case state do
       state when state in [:won, :lost] -> # do things
       :pending -> # do things
       _else -> # do other things
    end
end
Run Code Online (Sandbox Code Playgroud)

关于相同函数名的不同函数子句的放置,如果它们没有组合在一起,elixir 将发出警告,因此最终只是您有责任以正确的顺序将它们组合在一起(它也会警告您,如果根据定义,任何分支都是不可到达的,就像在任何具有匹配项的特定分支之前放置一个 catch all 一样)。

但是我认为,例如,如果您对pending状态的匹配要求稍作更改,那么在我看来,以 erlang/elixir 方式编写它开始变得更加清晰。假设当处于状态时,pending有两种不同的执行路径,这取决于是轮到您还是其他什么。

现在你可以只用函数签名来编写 2 个特定的分支:

def make_move(game = %{ game_state: :pending, your_turn: true }, _guess) do
    # do stuff
end

def make_move(game = %{ game_state: :pending }, _guess) do
    # do stuff
end
Run Code Online (Sandbox Code Playgroud)

要在 JS 中做到这一点,你需要有另一个开关,或者另一个 if。如果您有更复杂的匹配模式,那么它很容易变得难以遵循,而在长生不老药上,我认为路径非常清晰。

如果其他条件可能更棘手,比如它是什么时候,:pending并且stack保存列表的键上没有任何东西,然后再次匹配变为:

def make_move(game = %{ game_state: :pending, your_turn: true, stack: [] }, _guess) do

或者,如果有另一个分支取决于堆栈中的第一项是否特定:

def make_move(game = %{ game_state: :pending, your_turn: true, player_id: your_id, stack: [%AnAlmostTypedStruct{player: your_id} | _] }, _guess) do

这里 erlang/elixir 只会匹配它,如果your_id它在模式中使用的两个地方都相同。

而且,您在 JS 中说“没有幻想”,但是在 Elixir/Erlang 中,不同的函数头/arity/模式匹配没什么特别的,就像该语言在低得多的级别支持基于 switch/case 的语句一样(在模块编译级别?)。

我希望在 JS 中有有效的模式匹配和不同的函数子句(不仅仅是解构)。