Skip to content

Instantly share code, notes, and snippets.

@Lichers0
Forked from kyrylo/constant-lookup-in-ruby.md
Created April 22, 2023 11:47
Show Gist options
  • Save Lichers0/070f3a1ece2b03f2eb91bff4e15dd15d to your computer and use it in GitHub Desktop.
Save Lichers0/070f3a1ece2b03f2eb91bff4e15dd15d to your computer and use it in GitHub Desktop.

Revisions

  1. @kyrylo kyrylo revised this gist Sep 2, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -125,7 +125,7 @@ A::B::C.class_eval do
    # A value of 42 for X. C is asked for X, it is not found, so
    # the calling scope(main/Object) is asked next. It is found
    # as Object::X and returns a value of 42. The lexical scope does
    # starts at C but traverses into the calling scope and not the
    # start at C but traverses into the calling scope and not the
    # definition of A::B::C.
    #
    assert X == 42 # => true
  2. @kyrylo kyrylo revised this gist Sep 2, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -29,7 +29,7 @@ module A
    D = 5
    module B
    module C
    # C is asked does its constant table have 'D'. it does not.
    # C is asked if its constant table has 'D'. It's not.
    # The next scope is B. it also does not have 'D' in its
    # constant table. The next scope is A. A::D is found and
    # the lookup stops.
    @@ -90,7 +90,7 @@ class A
    end
    end

    # A::C is asked does its constant table have 'X', it does not.
    # A::C is asked if its constant table has 'X', It's not.
    # Next its superclass is asked: A::B. it does own the constant 'X'
    # and returns a value of 5. A lexical lookup(starting at A::C) would
    # have found 42(A::X)..
  3. Robert revised this gist Sep 2, 2013. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -29,7 +29,7 @@ module A
    D = 5
    module B
    module C
    # C is asked does its constant table have 'D'. it doe not.
    # C is asked does its constant table have 'D'. it does not.
    # The next scope is B. it also does not have 'D' in its
    # constant table. The next scope is A. A::D is found and
    # the lookup stops.
    @@ -68,7 +68,7 @@ class A::B::C < D
    # D(the superclass of C), then its superclass(Object) is asked,
    # at which point a NameError is raised.
    #
    X
    X # => 5
    end
    ```

    @@ -95,7 +95,7 @@ end
    # and returns a value of 5. A lexical lookup(starting at A::C) would
    # have found 42(A::X)..
    #
    assert A::C::X == 5
    assert A::C::X == 5 # => true
    ```

    __LEXICAL LOOKUP AND CLASS/INSTANCE_EVAL__
    @@ -128,7 +128,7 @@ A::B::C.class_eval do
    # starts at C but traverses into the calling scope and not the
    # definition of A::B::C.
    #
    assert X == 42
    assert X == 42 # => true
    end
    ```

  4. Robert revised this gist Sep 2, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -121,7 +121,7 @@ class A
    end


    C.class_eval do
    A::B::C.class_eval do
    # A value of 42 for X. C is asked for X, it is not found, so
    # the calling scope(main/Object) is asked next. It is found
    # as Object::X and returns a value of 42. The lexical scope does
  5. Robert revised this gist Sep 2, 2013. No changes.
  6. Robert revised this gist Sep 2, 2013. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -139,6 +139,9 @@ contexts. You can follow the codepath much easier. To finish up an
    example of how those codepaths can be followed:

    ```ruby
    require "test/unit"
    include Test::Unit::Assertions

    class A
    class B
    # The lookup path for X (lexical lookup):
  7. Robert revised this gist Sep 2, 2013. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -92,7 +92,8 @@ end

    # A::C is asked does its constant table have 'X', it does not.
    # Next its superclass is asked: A::B. it does own the constant 'X'
    # and returns a value of 5. A lexical lookup would have found 42.
    # and returns a value of 5. A lexical lookup(starting at A::C) would
    # have found 42(A::X)..
    #
    assert A::C::X == 5
    ```
  8. Robert revised this gist Sep 2, 2013. 1 changed file with 5 additions and 6 deletions.
    11 changes: 5 additions & 6 deletions constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -111,22 +111,21 @@ include Test::Unit::Assertions

    X = 42


    class A
    X = 5
    class B
    class C
    end
    class C
    end
    end
    end


    C.class_eval do
    # A value of 42 for X. C is asked for X, it is not found, so
    # the calling scope(main/Object) is asked next. It is found
    # as Object::X and returns a value of 42. The important
    # thing to note is that it was not found by going through
    # the ancestry tree of C.
    # as Object::X and returns a value of 42. The lexical scope does
    # starts at C but traverses into the calling scope and not the
    # definition of A::B::C.
    #
    assert X == 42
    end
  9. Robert revised this gist Sep 2, 2013. 1 changed file with 7 additions and 3 deletions.
    10 changes: 7 additions & 3 deletions constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -110,12 +110,16 @@ require "test/unit"
    include Test::Unit::Assertions

    X = 42
    class B


    class A
    X = 5
    class B
    class C
    end
    end
    end

    class C < B
    end

    C.class_eval do
    # A value of 42 for X. C is asked for X, it is not found, so
  10. Robert revised this gist Sep 2, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -92,7 +92,7 @@ end

    # A::C is asked does its constant table have 'X', it does not.
    # Next its superclass is asked: A::B. it does own the constant 'X'
    # and returns its value. A lexical lookup would have found 42.
    # and returns a value of 5. A lexical lookup would have found 42.
    #
    assert A::C::X == 5
    ```
  11. Robert revised this gist Sep 2, 2013. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -22,6 +22,9 @@ the ancestry tree.
    __LEXICAL LOOKUP__

    ```ruby
    require "test/unit"
    include Test::Unit::Assertions

    module A
    D = 5
    module B
  12. Robert created this gist Sep 2, 2013.
    152 changes: 152 additions & 0 deletions constant-lookup-in-ruby.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    Constant lookup in Ruby can happen lexically or through the
    ancestry tree of the receiver(a class or module). You can identify
    which lookup rules are being applied by the context you're in or by
    the syntax being used to define a class or module.

    A class body that is defined as `class A::B::C; …; end` will lookup
    constants through the ancestry tree when a constant is evaluated in
    its class body. Anytime you see `A::B::C` being used as syntax to
    define a class or lookup the value of a constant the ancestry tree
    is being used for the lookup.

    How would we apply lexical lookup in the definition of `A::B::C` if
    we know how to apply ancestry lookup? The syntax you use for the
    definition is what makes the difference. The first example defines
    a module with lexical constant lookup while the second example is
    defined to use the ancestry tree for lookup.

    The examples come with comments that explain what is happening and
    what it means for a constant to be looked up 'lexically' or through
    the ancestry tree.

    __LEXICAL LOOKUP__

    ```ruby
    module A
    D = 5
    module B
    module C
    # C is asked does its constant table have 'D'. it doe not.
    # The next scope is B. it also does not have 'D' in its
    # constant table. The next scope is A. A::D is found and
    # the lookup stops.
    #
    # the pattern to see here is that lookup is happening by
    # traversing back through the nested scopes. this is said
    # to be a lexical constant lookup. Anytime a class or module
    # body is defined like this constant lookup happens lexically.
    #
    assert D == 5
    end
    end
    end
    ```

    __ANCESTRY LOOKUP__

    ```ruby

    class D
    X = 5
    end

    class A::B::C < D
    # A constant lookup through the ancestry tree is implied
    # from the syntax we used to define the class. Anytime you
    # see the syntax A::B::C (in a class definition or expression)
    # you know constant lookup is going through the ancestry tree.
    #
    # C is asked does 'X' exist in its constant table, it does not.
    # Next the superclass of C is asked, which is D. D finds 'X' in
    # its constant table with a value of 5.
    #
    # neither B or A are asked for the constant like in a lexical
    # lookup. If X were not defined, C would be asked first, then
    # D(the superclass of C), then its superclass(Object) is asked,
    # at which point a NameError is raised.
    #
    X
    end
    ```

    What about the `A::B::C` syntax to read the value of a constant? we
    know the lookup will go through the ancestry tree but lets take a
    closer look:

    ```ruby
    require "test/unit"
    include Test::Unit::Assertions

    class A
    X = 42
    class B
    X = 5
    end

    class C < B
    end
    end

    # A::C is asked does its constant table have 'X', it does not.
    # Next its superclass is asked: A::B. it does own the constant 'X'
    # and returns its value. A lexical lookup would have found 42.
    #
    assert A::C::X == 5
    ```

    __LEXICAL LOOKUP AND CLASS/INSTANCE_EVAL__

    A class eval or instance eval will look for constants lexically. The
    difference about a lexical lookup in a class body definition and a
    block passed to class_eval is that class_eval will lookup from the
    calling scope and not the scope of 'self'(a Class or Module). An
    example(with comments) to illustrate:

    ```ruby
    require "test/unit"
    include Test::Unit::Assertions

    X = 42
    class B
    X = 5
    end

    class C < B
    end

    C.class_eval do
    # A value of 42 for X. C is asked for X, it is not found, so
    # the calling scope(main/Object) is asked next. It is found
    # as Object::X and returns a value of 42. The important
    # thing to note is that it was not found by going through
    # the ancestry tree of C.
    #
    assert X == 42
    end
    ```

    __FINISHING UP__

    I think it is useful to know how constant lookup happens and in what
    contexts. You can follow the codepath much easier. To finish up an
    example of how those codepaths can be followed:

    ```ruby
    class A
    class B
    # The lookup path for X (lexical lookup):
    # C -> B -> A -> calling scope(main/Object).
    class C
    assert_raise(NameError) { X }
    end
    end
    end

    class A; end
    class D < A; end
    class A::B::C < D
    # The lookup path for X (ancestor lookup):
    # C -> D -> A -> Object
    assert_raise(NameError) { X }
    end
    ```