什么时候 Elixir 的特性变成了误导?

At what point does Elixir features becomes a misdirection?

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

我仍在学习 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

可以在 Javascript 中毫无幻想地写成:

const makeMove = (game, guess) => {
  switch(game.state) {
    case 'won':
      return makeMoveFinalState();
    case 'lost':
      return makeMoveFinalState();
    case 'pending':
      return makeMovePending();
  }
}

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

除此之外,我觉得这是一种误导,因为你不能 100% 确定一个案例会在那个通用 make_move 函数中结束,除非你事先知道所有其他函数和签名类型,而有条件的,你有一个更清晰的流程路径。

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

我认为这主要归结为偏好,通常带有简单条件的模式匹配的简单练习不会显示 "clarity" 模式匹配可以提供的范围。但我怀疑是因为我更喜欢模式匹配,无论如何,我要咬一口。

在这种情况下,开关可以说更具可读性和直接性,但请注意,没有什么可以阻止您在 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

关于同一个函数名的不同函数子句的放置,如果它们没有组合在一起,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

要在 JS 中执行此操作,您需要有另一个开关或另一个 if。如果你有更复杂的匹配模式,那么它很容易变得更难遵循,而在 elixir 上,我认为路径非常清晰。

如果其他条件可能更棘手,比如当它是 :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里说的是"without fanciness",但是异函数heads/arity/pattern匹配在Elixir/Erlang里没有什么特别的,就好像语言支持switch/case一样基于低得多级别的语句(在模块编译级别?)。

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