Save 37% off PRO during our Black Friday Sale! »

The Curious Incident of the Shadow in the Run-Time

The Curious Incident of the Shadow in the Run-Time

A presentation about the perils of shadowing in Ruby. It is an adaptation of my blog post The Curious Incident of the Shadow in the Run-Time (https://paulfioravanti.com/blog/2018/08/20/the-curious-incident-of-the-shadow-in-the-run-time/)

Presented at:

- Ruby on Rails Oceania Sydney meetup on 9 July 2019.
- Melbourne Ruby meetup on 27 May 2020 (via Zoom)

Presentation slide deck markdown and speaker notes (useable in Deckset 2):
https://github.com/paulfioravanti/presentations/tree/master/the_curious_incident_of_the_shadow_in_the_run_time

Abda861707b1e78e0fce47ced55f84ee?s=128

Paul Fioravanti

May 27, 2020
Tweet

Transcript

  1. The Curious Incident of the SHADOW in the Run-Time

  2. None
  3. None
  4. The

  5. The Curious

  6. The Curious Incident

  7. The Curious Incident of the SHADOW

  8. The Curious Incident of the SHADOW in the Run-Time

  9. SHADOW

  10. VARIABLE SHADOWING

  11. shadow.rb

  12. shadow.rb x = 42

  13. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" }
  14. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" } $ ruby shadow.rb x is 0 x is 1 x is 2
  15. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" } $ ruby shadow.rb x is 42 x is 42 x is 42
  16. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" } $ ruby shadow.rb x is 0 x is 1 x is 2
  17. DOES RUBY CARE ABOUT SHADOWING?

  18. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" }
  19. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" } $ ruby -w shadow.rb
  20. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" } $ ruby -w shadow.rb shadow.rb:2: warning: shadowing outer local variable - x shadow.rb:1: warning: assigned but unused variable - x x is 0 x is 1 x is 2
  21. None
  22. None
  23. BUT WHY THOUGH?

  24. shadow.rb x = 42 3.times { |x| puts "x is

    #{x}" }
  25. shadow.rb x = 42 3.times do |x| puts( "Local x

    is #{x} and "\ "outer x is #{'What goes here??'}" ) end
  26. shadow.rb y = 42 3.times { |x| puts "x is

    #{x} and y is #{y}" }
  27. shadow.rb y = 42 3.times { |x| puts "x is

    #{x} and y is #{y}" } $ ruby -w shadow.rb x is 0 and y is 42 x is 1 and y is 42 x is 2 and y is 42
  28. "[Matz] thinks that shadowing by itself is a bad habit

    and should be discouraged." — Shouhei Urabe, quo/ng Matz
  29. ShadowingOuterLocalVariable

  30. None
  31. None
  32. INSTANCE METHOD SHADOWING

  33. "In Ruby, local variable names and method names are nearly

    iden0cal."
  34. "If you have not assigned to one of these ambiguous

    names, Ruby will assume you wish to call a method."
  35. "Once you have assigned to the name, Ruby will assume

    you wish to reference a local variable."
  36. "The local variable is created when the parser encounters the

    assignment, not when the assignment occurs." — Local Variables and Methods Assignment sec9on of Ruby's syntax documenta9on
  37. TOP ‑ BOTTOM

  38. NAMES CAN CHANGE

  39. METHOD ‑ VARIABLE

  40. None
  41. person.rb

  42. class Person end

  43. class Person def initialize(name = nil) @name = name end

    end
  44. class Person attr_accessor :name def initialize(name = nil) @name =

    name end end
  45. class Person def name @name end def name=(new_name) @name =

    new_name end def initialize(name = nil) @name = name end end
  46. class Person attr_accessor :name def initialize(name = nil) @name =

    name end end
  47. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  48. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  49. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  50. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  51. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  52. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  53. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  54. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  55. $ irb irb(main):001:0> require "./person.rb" true irb(main):002:0>

  56. $ irb irb(main):001:0> require "./person.rb" true irb(main):002:0> Person.new("Matz").say_name

  57. $ irb irb(main):001:0> require "./person.rb" true irb(main):002:0> Person.new("Matz").say_name My name

    is nil nil
  58. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  59. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  60. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  61. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  62. $ irb irb(main):001:0> require "./person.rb" true irb(main):002:0> Person.new("Matz").say_name My name

    is nil nil irb(main):003:0>
  63. $ irb irb(main):001:0> require "./person.rb" true irb(main):002:0> Person.new("Matz").say_name My name

    is nil nil irb(main):003:0> Person.new.say_name
  64. $ irb irb(main):001:0> require "./person.rb" true irb(main):002:0> Person.new("Matz").say_name My name

    is nil nil irb(main):003:0> Person.new.say_name My name is "Unknown" nil
  65. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  66. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  67. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  68. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  69. class Person attr_accessor :name def initialize(name = nil) @name =

    name end def say_name if name.nil? name = "Unknown" end puts "My name is #{name.inspect}" end end
  70. None
  71. None
  72. irb(main):002:0> Person.new("Matz").say_name

  73. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: if name.nil? 14: name = "Unknown" 15: end 16: 17: puts "My name is #{name.inspect}" 18: end [1] pry(#<Person>)>
  74. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: if name.nil? 14: name = "Unknown" 15: end 16: 17: puts "My name is #{name.inspect}" 18: end [1] pry(#<Person>)> name nil [2] pry(#<Person>)>
  75. ERR... WHAT?

  76. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: if name.nil? 14: name = "Unknown" 15: end 16: 17: puts "My name is #{name.inspect}" 18: end [1] pry(#<Person>)> name nil [2] pry(#<Person>)>
  77. [1] pry(#<Person>)> name nil [2] pry(#<Person>)> next

  78. [1] pry(#<Person>)> name nil [2] pry(#<Person>)> next From: /person.rb @

    line 17 Person#say_name: 10: def say_name 11: binding.pry 12: 13: if name.nil? 14: name = "Unknown" 15: end 16: => 17: puts "My name is #{name.inspect}" 18: end [2] pry(#<Person>)>
  79. [2] pry(#<Person>)> next From: /person.rb @ line 17 Person#say_name: 10:

    def say_name 11: binding.pry 12: 13: if name.nil? 14: name = "Unknown" 15: end 16: => 17: puts "My name is #{name.inspect}" 18: end [2] pry(#<Person>)> name nil [3] pry(#<Person>)> exit My name is nil
  80. SPOOKY

  81. SPOOKY QUANTUM

  82. SPOOKY QUANTUM RUBY

  83. SPOOKY QUANTUM RUBY?

  84. None
  85. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts name.inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end [1] pry(#<Person>)>
  86. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts name.inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end [1] pry(#<Person>)> name nil
  87. [1] pry(#<Person>)> name nil [2] pry(#<Person>)> next "Matz" From: /person.rb

    @ line 14 Person#say_name: 10: def say_name 11: binding.pry 12: 13: puts name.inspect => 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end
  88. None
  89. defined?

  90. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end [1] pry(#<Person>)>
  91. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end [1] pry(#<Person>)> next "method"
  92. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end [1] pry(#<Person>)> defined?(name) "local-variable"
  93. None
  94. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end
  95. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end
  96. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end
  97. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end
  98. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end
  99. irb(main):002:0> Person.new("Matz").say_name From: /person.rb @ line 13 Person#say_name: 10: def

    say_name 11: binding.pry 12: => 13: puts defined?(name).inspect 14: if name.nil? 15: name = "Unknown" 16: end 17: 18: puts "My name is #{name.inspect}" 19: end
  100. None
  101. DON'T

  102. DON'T SHADOW

  103. DON'T SHADOW

  104. def say_name if name.nil? self.name = "Unknown" end puts "My

    name is #{name.inspect}" end
  105. def say_name name = self.name || "Unknown" puts "My name

    is #{name.inspect}" end
  106. def say_name name = self.name || "Unknown" puts "My name

    is #{name.inspect}" end def say_name name = name() || "Unknown" puts "My name is #{name.inspect}" end
  107. None
  108. THANKS! @paulfioravanti