Scoping in try/catch/after

53 views
Skip to first unread message

Jake Mitchell

unread,
Apr 12, 2012, 1:37:22 AM4/12/12
to elixir-l...@googlegroups.com
I've started a loose port of Ruby Koans to better understand the nuances of Elixir and share what I learn.  While writing tests for try/catch/after I realized I couldn't update variables defined outside the try, catch, or after clauses.

  test :affect_state_from_try_catch_after do
    defrecord StateTracker, from_try: :default, from_catch: :default, from_after: :default
    outside_state = StateTracker.new

    try do
      assert_equal :default, outside_state.from_try  # passes
      outside_state = outside_state.from_try(:new_value)
      assert_equal :new_value, outside_state.from_try  # passes
      throw :something
    catch: caught_value
      assert_equal :default, outside_state.from_catch  # passes
      outside_state = outside_state.from_catch(:new_value)
      assert_equal :new_value, outside_state.from_catch  # passes
    after:
      assert_equal :default, outside_state.from_after  # passes
      outside_state = outside_state.from_after(:new_value)
      assert_equal :new_value, outside_state.from_after  # passes
    end
   
    assert_equal :new_value, outside_state.from_try  # fails: nil
    assert_equal :new_value, outside_state.from_catch  # fails: nil
    assert_equal :new_value, outside_state.from_after  # fails: nil
  end

Documentation for the try defmacro says variables created inside the try clause can't be accessed externally.  What it doesn't address is reading or updating preexisting variables.  Based on the test above, reading from a preexisting variable is no problem, but updates don't take hold outside of the try/catch/after.

We can still affect the state of the outer scope using the result of the try statement, but it requires writing try statements like what's shown below.

  def some_function() 
    try_result = try do
                   do_something()
                 catch: caught_value
                   log_catch(caught_value)
                   {:thrown, caught_value}
                 after:
                   IO.puts "finished try/catch"
                 end
    process_result(try_result)
  end

I bring it up because many other languages permit changing the state surrounding the try statement from within all parts of the try statement.  For example, here's a Ruby method that's similar to the Elixir test, except it behaves much differently:

def affect_state_from_begin_rescue_ensure_test
  outside_state = {
    :from_begin => nil,
    :from_rescue => nil,
    :from_ensure => nil
  }
 
  begin
    outside_state[:from_begin] = :new_value
    raise
  rescue
    outside_state[:from_rescue] = :new_value 
  ensure
    outside_state[:from_ensure] = :new_value
  end
 
  puts "From begin: #{outside_state[:from_begin]}"  # :new_value
  puts "From rescue: #{outside_state[:from_rescue]}"  # :new_value
  puts "From ensure: #{outside_state[:from_ensure]}"  # :new_value
end

Not being able to affect surrounding state from the after clause is particularly strange to me.  Unlike the try and catch clauses, the after clause has no impact on the result of the overall try statement.  Additionally, we can't write to variables in the surrounding scope that could impact the return value of the enclosing function.  From what I've seen in examples and the Elixir codebase, the after clause has only been used to make system calls, such as interacting with the file system or writing to standard output  Many other languages let the "finally" clause affect outer state and thereby affect the enclosing function's return value.  Do we want that possibility in Elixir too?

It's not my intention to cause commotion over this.  Don't hesitate to bring factors I haven't addressed or share your own take on the matter.  I could easily be missing something, like relevant details about Erlang--definitely a newb here :)

Thanks!

-Jake

José Valim

unread,
Apr 12, 2012, 5:46:21 AM4/12/12
to elixir-l...@googlegroups.com, elixir-l...@googlegroups.com
Hello Jake,

Thanks for the feedback and trying out Elixir. Unfortunately the current limitation is intrinsic to the Erlang VM, so there isn't anything we can do. If you want to update the docs to make your case more clear, a pull request is very welcome.

Thanks,

Reply all
Reply to author
Forward
0 new messages