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

5

46

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...

Adaptation answered 15/1, 2015 at 19:23 Comment(5)
It's just shadowed after reassignment. Data is still immutable. Closure example from Joe Armstrong's blog should make it clear. In my opinion it's confusing.Sucrose
I think at some point we need to add something to the Elixir docs to explain this since this question comes up on a regular basis.Mccloskey
I understand that the ^ operator is necessary to be able to perform a pattern match and an assignment on the left term. What is really not clear for me is why the default behavior is "shadowing/re-assigning" rather than pattern matching: - In my code I really have very few need of re-assignment; - in a code that claim to have immutable data, I would expect that re-assignment is explicit.Jecon
@Jecon since data is immutable there is no side effect that can change what a variable points to. The only way to change what a variable points to is to re-assign it. Since this only happens explicitly, it is a convenience to not have to come up with a new identifier when you no longer need the old value.Ecotone
Working link to blog post in first comment: joearms.github.io/published/2013-05-31-a-week-with-elixir.htmlRelief
K
63

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]
Kowatch answered 16/1, 2015 at 0:0 Comment(2)
I'd rather say x = 1; [1, x, 3] = [1, 2, 3] is equivalent to the following in Erlang: X = 1. f(X). [1, X, 3] = [1, 2, 3]Diagnostician
In the shell - yes, but you can use this also in scripts, where f/1 is not possible, that is why I compared it to creating new variable.Kowatch
E
25

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.

Ecotone answered 16/1, 2015 at 6:20 Comment(1)
That last example code block contains the first example I've yet seen of using the pin operator that isn't so contrived that it's unclear why one would use it.Disquisition
H
1

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

current_password = 'secret-1'

{ new_password, ^current_password } = { 'secret-2', 'secret-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
Hobbes answered 28/4, 2020 at 11:18 Comment(2)
Shouldn't the ^current_password be compared to secret-1? { new_password, ^current_password } = { 'secret-2', 'secret-1'}Solfeggio
Yes, you are correct. However, the example is written this way so that a MatchError occurs. Basically, it's a rewrite of the javascript example above.Hobbes
S
1

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
Solfeggio answered 20/12, 2020 at 15:32 Comment(0)
E
1

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

Epinasty answered 14/11, 2021 at 12:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.