"pin" 运算符是做什么用的,Elixir 变量是可变的吗?

What is the "pin" operator for, and are Elixir variables mutable?

目前正在尝试了解 Elixir 中的“^”运算符。 来自网站:

The pin operator ^ can be used when there is no interest in rebinding a variable but rather in matching against its value prior to the match:

来源 - http://elixir-lang.org/getting_started/4.html

考虑到这一点,您可以将新值附加到符号,如下所示:

iex> x = 1  # Outputs "1"
iex> x = 2  # Outputs "2"

我也可以:

iex> x = x + 1  # Outputs "3"!

所以我的第一个问题是; Elixir 变量是否可变? 如果是这样的话,看起来肯定是这样……在函数式编程语言中难道不应该吗?

现在我们来看看“^”运算符...

iex> x = 1  # Outputs "1"
iex> x = 2  # Outputs "2"
iex> x = 1  # Outputs "1"
iex> ^x = 2 # "MatchError"
iex> ^x = 1  # Outputs "1"

我认为“^”的作用是将"x"锁定到绑定到它的最后一个值。仅此而已吗? 为什么不像 Erlang 本身那样确保所有 'matches'/赋值都是不可变的呢?

我刚刚习惯了...

Elixir 中的数据仍然是不可变的,但有几个简写形式,可以让您减少输入或不用担心找到新名称。在 Erlang 中,你经常可以看到这样的代码:

SortedList = sort(List),
FilteredList = filter(SortedList),
List3 = do_something_with(FilteredList),
List4 = another_thing_with(List3)

在 Elixir 中,你可以这样写:

list = sort(list)
list = filter(list)
list = do_something_with(list)
list = another_thing_with(list)

这个是一模一样的,只是看起来好一点。当然最好的解决方案是这样写:

list |> sort |> filter |> do_something |> another_thing_with

每次,您将新事物分配给 list 变量,您将获得新实例:

iex(1)> a = 1
1
iex(2)> b = [a, 2]
[1, 2]
iex(3)> a = 2
2
iex(4)> b
[1, 2] # first a did not change, it is immutable, currently a just points to something else

你只是说,你不再对旧的 a 感兴趣,让它指向别的东西。如果您有 Erlang 背景,您可能知道 shell.

中的 f 函数
A = 1.
f(A).
A = 2.

在 Elixir 中,您不必编写 f。它会自动为您完成。这意味着,每次,您在模式匹配的左侧都有变量,您正在为其分配新值。

如果没有 ^ 运算符,您将无法在模式匹配的左侧有一个变量,因为它会从右侧获取新值。 ^ 表示 不要为这个变量分配新的东西 - 将其视为文字值.

这就是为什么在 Elixir

x = 1
[1, x, 3] = [1, 2, 3]

在 Erlang 中等同于:

X = 1,
[1, CompletelyNewVariableName, 3] = [1, 2, 3]

和:

x = 1
[1, ^x, 3] = [1, 2, 3]

相当于:

x = 1
[1, 1, 3] = [1, 2, 3]

在 Erlang 中是:

X = 1,
[1, X, 3] = [1, 2, 3]

elixir 中的数据是不可变的,但变量是可重新分配的。使 elixir 有点混乱的是您看到的组合赋值和模式匹配。

当你在左边的变量引用中使用等号时,elixir 将首先对结构进行模式匹配,然后执行赋值。当左边只有一个唯一的变量引用时,它将匹配任何结构,因此将像这样分配:

 a = 1 # 'a' now equals 1
 a = [1,2,3,4] # 'a' now equals [1,2,3,4]
 a = %{:what => "ever"} # 'a' now equals %{:what => "ever"}

当你在左侧有一个更复杂的结构时,elixir 将首先对结构进行模式匹配,然后执行赋值。

[1, a, 3] = [1,2,3] 
# 'a' now equals 2 because the structures match
[1, a] = [1,2,3] 
# **(MatchError)** because the structures are incongruent. 
# 'a' still equals it's previous value

如果您想根据变量的内容进行值匹配,您可以使用引脚“^”:

a = [1,2] # 'a' now equals [1,2]
%{:key => ^a} = %{:key => [1,2]} # pattern match successful, a still equals [1,2]
%{:key => ^a} = %{:key => [3,4]} # **(MatchError)**

这个人为设计的例子也可以用 'a' 写在右边,没有大头针:

%{:key => [1,2]} = %{:key => a}

现在假设您想将一个变量分配给结构的一部分,但前提是该结构的一部分与存储在 'a' 中的内容匹配,在 elixir 中这是微不足道的:

a = %{:from => "greg"}
[message, ^a] = ["Hello", %{:from => "greg"}] # 'message' equals "Hello"
[message, ^a] = ["Hello", %{:from => "notgreg"}] # **(MatchError)**

在这些简单的示例中,pin 和模式匹配的使用并不会立即变得非常有价值,但是随着您了解更多 elixir 并越来越多地开始进行模式匹配,它会成为 elixir 提供的表现力的一部分。

The best way to understand Elixir's pin operator ^ is with relatable examples.

问题:

允许用户在更改密码之前更改密码,他们必须提供新密码和之前的密码。

解决方案:

在像 JavaScript 这样的语言中,我们可以像这样编写一个简单的解决方案

let current_password = 'secret-1';

const params = {
  new_password: 'secret-2',
  current_password: 'secret-2'
}

if (current_password !== params.current_password) {
  throw "Match Error"
}

以上将抛出 Match Error 因为用户提供的密码与他们当前的密码不匹配

使用 Elixir 的 pin operator 我们可以将上面的代码写成

current_password = 'secret-1'

{ new_password, ^current_password } = { 'secret-2', 'secret-2'}

以上也会引发 MatchError 异常

解释:

使用 pin 运算符 ^ 对现有变量的值进行模式匹配。在上面的 Elixir 示例中,变量 new_password 绑定到元组中的第一项(用 {} 表示的 Elixir 数据结构),而不是重新绑定 current_password 变量,我们模式匹配它的现有价值。

现在 Elixir 文档中的这个示例应该有意义了。

iex(1)> x = 1
1
iex(2)> ^x = 1 # Matches previous value 1
1
iex(3)> ^x = 2 # Does not match previous value 
** (MatchError) no match of right hand side value: 2

这是我的简约方法:

等号 (=) 不仅仅是赋值,这里发生了两件事:

  1. 模式匹配。
  2. 如果模式匹配,则这会导致从右到左的赋值。否则报错。

想想代数中的“=”,这表示等式的左边和右边表示相同,所以如果 x = 1,则 x 的唯一值是 1。

iex(1)> x = 1 # 'x' matches 1
1
iex(2)> x # inspecting the value of 'x' we get 1, like in other languages
1
iex(3)> x = 2 # 'x' matches 2
2
iex(4)> x # now 'x' is 2
2

那么我们如何使用 'x' 来比较而不是给它赋一个新值呢?

我们需要使用pin操作符^:

iex(5)> ^x = 3
** (MatchError) no match of right hand side value: 3

我们可以看到 'x' 值仍然是 2。

iex(5)> x
2

模式匹配将左侧的值与右侧的值相匹配。如果匹配且左侧包含变量,则将右侧的相应值分配给变量。

Caret(^) 运算符将变量固定在其值上,并防止在使用模式匹配时对此变量进行任何赋值。

参考:https://medium.com/@Julien_Corb/understand-the-pin-operator-in-elixir-a6f534d865a6