In-depth article about scopes in Elixir

730 views
Skip to first unread message

Alexei Sholik

unread,
Apr 3, 2014, 10:56:49 AM4/3/14
to elixir-l...@googlegroups.com
https://github.com/alco/elixir/wiki/Scoping-Rules-in-Elixir-(and-Erlang)

I would appreciate people reviewing this for correctness. I hope it's useful enough to be placed in the official wiki.

As an aside, I'm was wondering about the rationale behind "conditional clause scope". I think it'd be more natural for conditional clauses to work like generators in comprehensions: variable bindings introduced in a condition should only be visible inside the body, like this:

    if a = some_func() do a else :error end

If I want 'a' to be accessible after the condition, I'll just place it one line above:

    a = some_func()
    if a do a else :error end

Same goes for cond. If I want to leak a variable from a conditional clause I can just bind it in the body:

    cond do
      a0 = some_func() -> a = a0
    end

Introducing this change will make conditionals work similar to case and receive and allow me to remove the "Conditional Clause Scope" section from the doc which also means there will be less rules to remember.

Cheers.

--
Best regards
Alexei Sholik

Eric Meadows-Jönsson

unread,
Apr 3, 2014, 12:43:10 PM4/3/14
to elixir-l...@googlegroups.com

Great article Alexei!

Just a few things I want to comment on:

Although variables defined at the top level are accessible inside modules, the modules cannot modify them (just like closures). I couldn’t find an explanation of this.

defmodule M do

  x = 1

  # shadowing in action: the 'x' in the argument list creates a variable
  # local to the function clause's body and has nothing to do with the
  # previously defined 'x'
  def baz(x), do: x

end

I wouldn’t call the above shadowing, since there is no way to refer to the module level x from inside the function. This would also be a good place that to explain that things inside unquote uses the module level context.

I agree that cond should no leak in clause heads, this is inconsistent with case today. Although case and receive‘s -> are matchers, while cond is just a normal expression, so they are not fully comparable.

I don’t really see the rationale for if to not leak in the head. The reason comprehensions don’t leak is because they are implemented with anonymous functions (we have discussed making the new comprehensions leak so that the variables would work as accumulators in a reduce). Having a special scoping rule for if‘s head only complicates things, why does the head not leak but the body does? I am also pretty sure we cannot implement this without making if a special form (this might be the reason why cond has the current behaviour).

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
Eric Meadows-Jönsson

Alexei Sholik

unread,
Apr 3, 2014, 2:15:12 PM4/3/14
to elixir-l...@googlegroups.com
Hi Eric,

Although variables defined at the top level are accessible inside modules, the modules cannot modify them (just like closures). I couldn’t find an explanation of this.

Good point, I'll make it clearer.

I wouldn’t call the above shadowing, since there is no way to refer to the module level x from inside the function. This would also be a good place that to explain that things inside unquoteuses the module level context.

This is a fine observation. I agree with you. I'll provide a different example of shadowing, one with `case` and a `fn`.

I don’t really see the rationale for if to not leak in the head. 

It's interesting how you think making if's head not leak bindings is a special case. To me, `if` is just a special case of `cond`:

cond do
  <condition> -> <then_body>
  true              -> <else_body>
end

if <condition> do <then_body> else <else_body>

Therefore I don't see why it should leak bindings from the <condition> if `cond` won't.

Thanks for you excellent comments!
Best regards
Alexei Sholik

Alexei Sholik

unread,
Apr 3, 2014, 2:19:01 PM4/3/14
to elixir-l...@googlegroups.com
I've just found out that `case a = ... do` will also introduce the binding for 'a' into the surrounding scope.

If that's how it's supposed to be, I can live with that. I'll just add another example to the article to explain that `if` is more like `case` in this case.

José Valim

unread,
Apr 3, 2014, 2:20:01 PM4/3/14
to elixir-l...@googlegroups.com
The article is great Alexei! I will complement Eric's response.

When we are talking about variables in Elixir there are effectively three different constructs:

1. constructs that introduce a clean scope: top-level scope and named functions
2. constructs that introduce a new scope but with access to the parent: anonymous functions and try
3. all other constructs work in the same scope and therefore modify the same scope 

Modules and comprehensions are literally implemented as anonymous functions and that's why they can access the parent scope but not change it.

I am not sure if it is worthy talking about shadowing. In Elixir you can always redefine a variable (different from Erlang) and there is no leaky shadowing (as in Ruby where a variable from a block (inner scope) can shadow the parent one).

You have noted that variables on the lhs of case/cond/receive are only available during that clause. But this is actually true for *all* Elixir constructs, so they are not a special case (the fact variables leak on the lhs of -> in cond is a bug to be fixed after v0.13).

Everything in the standard library is bound by the three rules above. Or you introduce a clean slate, or you have access to the parent one, or you are in the same scope except for variables on the lhs of ->.

That's why I don't believe a module scope exists. If a module scope exists, a protocol scope should exist. To me, defmodule is a DSL for defining modules, as defprotocol is a DSL for defining protocols. Some constructs like @ only works inside modules by default, some constructs like defimpl (remember they are both macro calls) only works inside protocols.

Similarly if/and/etc are all bound to one of the three rules above. In this case, if is bound to rule 3. It is in the same scope and, as it doesn't have a lhs of ->, it doesn't have a special case.

Finally, if we are talking about the lexical scope, excluding variables, things are easier. Everything that introduces a new branching is a new lexical scope:

# new scope
defmodule Foo do
  # new scope
  def foo do
    # new scope
    if true do
      # new scope
    else
      # new scope
    end
  end
end


José Valim
Skype: jv.ptec
Founder and Lead Developer

Alexei Sholik

unread,
Apr 4, 2014, 2:08:18 PM4/4/14
to elixir-l...@googlegroups.com
I have updated the article: mostly reworded the introduction, description of modules and named functions, and reduced the number of scope types.


I agree with José's perspective: there are only two types of scope and constructs like `case` simply modify the surrounding scope.

I have added a new section that describes how import, require, and alias treat scopes in a slightly different manner.

Thanks again for the feedback all!


--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Best regards
Alexei Sholik

Alexei Sholik

unread,
Apr 4, 2014, 2:16:14 PM4/4/14
to elixir-l...@googlegroups.com
I've just updated the example with `cond` (in the section https://github.com/alco/elixir/wiki/Scoping-Rules-in-Elixir-%28and-Erlang%29#case-like-clauses). It assumes the bug that causes leaking of variables bound in clause conditions is fixed.

José Valim

unread,
Apr 4, 2014, 2:23:36 PM4/4/14
to elixir-l...@googlegroups.com
Thanks Alexei!

I've just updated the example with `cond` (in the section https://github.com/alco/elixir/wiki/Scoping-Rules-in-Elixir-%28and-Erlang%29#case-like-clauses). It assumes the bug that causes leaking of variables bound in clause conditions is fixed.

The bug will fixed only on v0.13.1. :S

Alexei Sholik

unread,
Apr 4, 2014, 3:32:43 PM4/4/14
to elixir-l...@googlegroups.com
Good to know. I've added a note about the bug.


--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

he...@work.capital

unread,
Apr 20, 2016, 6:33:22 AM4/20/16
to elixir-lang-talk
Hi Alexei,
very good article! I am trying to figure out if the "end" for functions and modules are really necessary, so we could write less LOC,
pls, let me know if you have any example where they are completely necessary.
best
Henry
Reply all
Reply to author
Forward
0 new messages