简体   繁体   中英

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

Currently trying to understand the "^" operator in Elixir. From the website:

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:

Source - http://elixir-lang.org/getting_started/4.html

With this in mind, you can attach a new value to a symbol like so:

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

I can also do:

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

So my first question is; Are Elixir variables mutable? It sure looks like if that's the case... Shouldn't that be possible in a functional programming language?

So now we come to the "^" operator...

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

I think the effect of "^" is to lock "x" to the last value binded to it. Is that all there is to it? Why not just ensure that all 'matches'/assignments are immutable like Erlang itself?

I was just getting used to that...

The data in Elixir is still immutable, but there are couple of shorthands, that let you type less or don't worry about finding new names. In Erlang, you could often see code like this:

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

In Elixir, you could just write:

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

This is exactly the same, but it looks a little better. Of course the best solutions would be to write like this:

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

Every time, you assign new thing to list variable, you get new instance:

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

You just say, that you are no longer interested in the old a and let it point to something else. If you are coming from Erlang background, you probably know the f function from shell.

A = 1.
f(A).
A = 2.

In Elixir you just don't have to write the f . It is done automatically for you. This means, that every time, you have variable on the left side of the pattern match, you are assigning new value to it.

Without the ^ operator, you wouldn't be able to have a variable on the left side of pattern match, because it would get new value from the right side. ^ means do not assign new things to this variable - treat it as a literal value .

That is why in Elixir

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

is equivalent in Erlang to:

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

and:

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

is equivalent to:

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

which in Erlang is:

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

Data in elixir is immutable, variables though are re-assignable. What can make elixir slightly confusing is the combined assignment and pattern matching that you are seeing.

When you use the equals sign with a variable reference on the left elixir will first pattern match the structure, and then perform an assignment. When you have just a sole variable reference on the left, it will match any structure and so will be assigned like so:

 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"}

When you have a more complex structure on the left elixir will first pattern match the structures, then perform the assignment.

[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

If you want to value match against the contents of a variable you can use the 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)**

This contrived example could also have been written with 'a' on the right hand side and without the pin:

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

Now say you wanted to assign a variable to part of a structure but only if part of that structure matched something stored in 'a', in elixir this is trivial:

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

In these simple examples the use of the pin and pattern matching isn't immediately super valuable, but as you learn more elixir and start pattern matching more and more it becomes part of the expressiveness that elixir affords.

Here is my minimalistic approach:

The equal symbol (=) is not just assignation, two things happen here:

  1. pattern matching.
  2. if the pattern matches, then this leads to an assignment from right to left. Otherwise, an error is reported.

Think of "=" as in algebra, this indicates the left and the right side of the equation are representing the same, so if you have x = 1, the only value for x is 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

so how can we use 'x' to compare and not to assign it a new value?

We need to use the pin operator ^:

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

we can see that 'x' value is still 2.

iex(5)> x
2

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

Problem:

Users are allowed to change their passwords before they do, they will have to provide a new password and their previous password.

Solution:

In a language like JavaScript, we can write a naive solution like so

let current_password = 'secret-1'; const params = { new_password: 'secret-2', current_password: 'secret-2' } if (current_password !== params.current_password) { throw "Match Error" }

The above will throw a Match Error because the user's supplied password does not match their current password

Using Elixir's pin operator we can write the above as

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

The above will also rais a MatchError exception

Explanation:

Use the pin operator ^ to pattern match against an existing variable's value. In the Elixir's example above, the variable new_password is bound to the first item in the tuple (Elixirs data structure represented with {} ), rather than rebinding the current_password variable, we pattern match against its existing value.

Now this example from Elixir's docs should make sense.

 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

Pattern matching matches the value on the left hand side with the value on the right hand side. if it matches and if the left hand side includes a variable, it assigns the corresponding value from the right hand side to the variable.

Caret(^) operator pins the variable on its value and prevent any assignment to this variable when using pattern matching.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM