Is the Pin operator backwards?

Elixir is touted as being a ‘Functional Language’ and the common characteristic of a functional language is that its ‘variables’ are immutable.

Other functional languages allow some tolerance for mutability, as long as some explicit ceremony is employed so that the developer knows he/she is ‘breaking the rules’.

it seems to me that the Pin operator should really be an ‘UnPin’ operator so that allowing a left side to be re-matched to a right side becomes an explicit operation i.e. the left side is immutable unless I use the ‘UnPin’ symbol - “I know that by writing ‘^a’ I am explicitly allowing a state change”.

Does this make sense or do I misunderstand something?

I’m not putting Elixir down - just trying to understand it.

Yes, I have read José’s github posting about why he allows re-matching. I am just thinking that allowing re-matching shouldn’t be the default behavior and that it doesn’t cost much time or effort to type one extra character to allow the re-match.

1 Like

Rebinding a variable does not mutate data. If I have:

x = %{foo: 1}
y = x
x = Map.put(x, :bar, 2)

y is still %{foo: 1}. The data itself is completely unchanged. We have shadowed x, but that isn’t the same thing as mutability. Data and bindings to data aren’t the same thing. It can be a difficult thing to see at first for some, but the “tolerance for mutability” you speak of from other functional languages doesn’t even apply here, since mutable data is not a feature available in the VM.

This is perhaps easier to see in a loop:

x = 1
for i <- 1..10 do
  x = i
end

If = were performing a mutation, x would be 10, but in fact it’s still just 1. All that happens is shadowing / rebinding, and that naturally doesn’t work across lexical scopes.

3 Likes

In Erlang, this does not work…

$ erl
Erlang/OTP 21 [erts-10.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Eshell V10.0  (abort with ^G)
1> A=10.          
10
2> A=11.
** exception error: no match of right hand side value 11

But in Elixir it works

$ iex
Erlang/OTP 21 [erts-10.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> a=10
10
iex(2)> a=11
11

Because underneath, it’s doing

A0 = 10.
A1 = 11.

You might not like it, because it feels like mutation, but it’s not, it’s rebinding. Just consider this as a syntactic sugar. It’s useful when doing

conn = conn |> apply_some_change()

In the end, it does generate valid bytecode for the BEAM, and immutability is respected.

BTW You might prefer Erlang syntax, but You would lose |> :slight_smile:

1 Like

You can use ETS for mutability if you really really need it (say, if you want a big data structure that must change often with minimal performance penalty; or caches).

The pin operator does not mutate. It allows you to match information stored in a variable instead of it being hardcoded. The official docs have pretty good examples, have you tried them in iex?

The = does not assign per se; it matches a pattern and rebinds if the match is successful (namely the variable is internally now a different variable entirely; using the same name is a syntax sugar, and that variable simply points to a new immutable piece of data now; the old value is discarded). It never assigns, as @benwilson512 mentioned.

1 Like

While technically correct I think it leaves the impression that you are mutating, especially for newcomers to the language. If I’d guess this behaviour was put in because of two things:

  1. Principle of least surprise, if you are coming from an imperative language (but quite surprising to an erlang developer)
  2. Because they’ve seen too much code like
A0 = do_something(A),
A1 = do_something_else(A0)

and found it unpleasant.

Personally I to some degree see rebinding with the same variable name as an opportunity to rewrite that piece of code. In erlang the A0..AN variables stick out a bit encouraging you to rewrite. I think the risk of allowing re-binding is that it can potentially make it harder for someone who thinks it is mutability to learn how to think functionally.

1 Like

I sympathize and I was quite shocked when I was learning Elixir, in several places. But the gulf between imperative and functional is big and there is not much we can do about it…

I agree, but IMO it all boils down to the discipline of the programmer in the end. I was using throwaway variables long before I ever heard of functional programming, for example. And then you have people writing an OOP library for Elixir. ¯_(ツ)_/¯

Some mind and brain bending is inevitable while one learns Elixir.

In the end it boils down to how adamant the programmer is about the way they want to do things.

1 Like

Fantastic responses!

Thank you all so much.

I suppose I just need to “retrain the brain” from a world of imperative programming to that of functional programming.

I had to go through a similar shift many, many years ago coming from iterative programming to the world of Prolog - you just have to learn to think differently ;).

I suppose a year from now, I’ll look back and laugh at myself for not appreciating the syntactic sugar that José took the time to build into Elixir.

Having said all that, I am really looking forward to exploring Elixir and becoming fluent in a ‘new’ way to program!

3 Likes

It would be nice if the pin operator was reversed but that would make the syntax quite cumbersome for some simple cases. Take:

^x = 10 vs x = 10

Also it would not be intuitive how to re-using a variable within a pattern match would work. Would you write {^x, ^x} = {20, 20}?

Honestly I’d probably have had elixir had two operators, one for assignment and matching with a pin, and another for matching and assigning only to new bindings else use a pin to override. The conflating of matching and the weird rebinding/assigning Elixir does right now is confusing to a lot of people.

So no, it’s not that the pin operator is backwards, it’s great for an ‘assign’ operator, the problem is that there isn’t a dedicated matching operator as well. Could use := for assigning and = for matching, or reverse it, or something else, or whatever. Someone could do it with a library.

How would that work in expressions where you bind some variables and match on others? Which operator would you use and why?

Also, what about pattern matching in case or function heads, where you don’t have the operator at all, yet still have the matching/binding behaviour.

6 Likes

It seems like you’re confusing mutation with re-binding. They’re not the same. Re-binding does not mutate any data; does not create any change that is visible outside the immediate scope.

José Valim wrote a great article on this subject – http://blog.plataformatec.com.br/2016/01/comparing-elixir-and-erlang-variables.

1 Like

Depends on the expression.

Personally I’d just not have implicit rebinding at all, rather just force rebinding to use := to be explicit but otherwise all matches in all cases otherwise would follow erlangs semantics, no pin operator needed (although I guess you could allow {:ok, :=rebind} = ... to allow in-match rebinding, which would still do the whole invert thing, or maybe :: or something, or ^ since it’s not used anymore with this style).

But yeah, the erlang method feels far more natrual to me, but since I came from erlang that could easily just be bias. ^.^;

I think that piping fixes most of the ‘naming’ issues, otherwise use better names and don’t append numbers.

1 Like