“pin”操作符是干啥用的,Elixir 变量是可变的吗?

Posted

技术标签:

【中文标题】“pin”操作符是干啥用的,Elixir 变量是可变的吗?【英文标题】:What is the "pin" operator for, and are Elixir variables mutable?“pin”操作符是干什么用的,Elixir 变量是可变的吗? 【发布时间】:2015-03-14 07:32:09 【问题描述】:

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

当对重新绑定不感兴趣时​​,可以使用 pin 运算符 ^ 一个变量,而是在匹配它之前的值 匹配:

来源 - 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 本身一样确保所有“匹配”/分配都是不可变的?

我只是习惯了……

【问题讨论】:

它只是在重新分配后被遮蔽。数据仍然是不可变的。 Joe Armstrong's blog 的闭包示例应该清楚。在我看来,这很令人困惑。 我认为在某些时候我们需要在 Elixir 文档中添加一些内容来解释这一点,因为这个问题经常出现。 我知道 ^ 运算符对于能够执行模式匹配和左项赋值是必需的。对我来说真正不清楚的是为什么默认行为是“隐藏/重新分配”而不是模式匹配: - 在我的代码中,我真的很少需要重新分配; - 在声称具有不可变数据的代码中,我希望重新分配是明确的。 @Pascal 因为数据是不可变的,所以没有副作用可以改变变量指向的内容。更改变量指向的唯一方法是重新分配它。由于这只是显式发生的,因此当您不再需要旧值时不必提出新标识符是很方便的。 第一条评论中博客文章的工作链接:joearms.github.io/published/2013-05-31-a-week-with-elixir.html 【参考方案1】:

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

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

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

【讨论】:

【参考方案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

【讨论】:

【参考方案3】:

了解 Elixir 的 pin 运算符 ^ 的最佳方式是使用相关示例。

问题:

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

解决方案:

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 绑定到元组中的第一项(以 表示的 Elixirs 数据结构),而不是重新绑定 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

【讨论】:

不应该将 ^current_passwordsecret-1 进行比较吗? new_password, ^current_password = 'secret-2', 'secret-1' 是的,你是对的。但是,示例是这样编写的,因此会发生 MatchError。基本上,它是对上面 javascript 示例的重写。【参考方案4】:

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

如果您想根据变量的内容进行值匹配,您可以使用 pin '^':

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 提供的表现力的一部分。

【讨论】:

最后一个示例代码块包含我见过的第一个使用 pin 运算符的示例,该示例不是那么做作,以至于不清楚为什么要使用它。【参考方案5】:

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]

【讨论】:

我宁愿说x = 1; [1, x, 3] = [1, 2, 3]在Erlang中等价于:X = 1. f(X). [1, X, 3] = [1, 2, 3] 在 shell 中 - 是的,但你也可以在脚本中使用它,f/1 是不可能的,这就是为什么我将它与创建新变量进行比较。

以上是关于“pin”操作符是干啥用的,Elixir 变量是可变的吗?的主要内容,如果未能解决你的问题,请参考以下文章

Linux是干啥用的?

java里HibernateCallback()是干啥用的

Java中的Statement类是干啥用的?

vb中的DoEvents是干啥用的?

OverrideAuthenticationAttribute 是干啥用的?

这个“1342177280”标志是干啥用的?