Your SlideShare is downloading. ×
0
Ruby
GotchasLast edited 2014-11-26
by Dave Aronson,
T. Rex of Codosaurus, LLC
Though "engineered to
maximize programmer
happiness", with the
"principle of least surprise",
Ruby still has gotchas.
This...
Don't quote me on this, but . . . .
x = 3
puts 'x = #{x}nx'
x = #{x}nx
puts "x = #{x}nx"
x = 3
x
String interpolation
(inc...
Only two things are false
(falsey): false, and nil.
Everything else is true
(truthy), even 0 (false in C),
"" (false in JS...
Symbols != strings.
Even if same, printed.
Remember which one to
use for what (args).
Ideally, take either: "Be
liberal in...
bash> irb-1.9
str = "string"
=> "string"
str[2]
=> "r"
# makes sense *now*
bash> irb-1.8
str = "string"
=> "string"
str[2]...
FOO = 5
=> 5
FOO = 7
(irb):3: warning:
already initialized
constant FOO
=> 7
FOO
=> 7
Constants Aren't (Part 1/2)
(Initial...
Even freezing doesn't
work for Fixnums.
It does work for arrays
(sort of) and most other
objects . . . he said
foreshadowi...
Some are more equal than others
Effectively:
== is the usual
(same value)
.eql? is value and class
(1 is Fixnum, 1.0 is Fl...
Effectively:
=== is "case equality", as in
case statements. A better name
(IMHO) might be ".describes?",
or overload ".inc...
&& has higher precedence
than =, so
x = true && false
means
x = (true && false)
and has lower precedence, so
x = true and ...
|| has higher precedence
than =, so
x = false || true
means
x = (false || true)
or has lower precedence so
x = false or tr...
Whitespace-insensitive?
NOT ALWAYS!
With multiple args:
- No parens, no problem.
- Parens w/o space, OK.
- Parens and spac...
def method; 42; end
num = 21
method/num
=> 2
method / num
=> 2
method/ num
=> 2
method /num
SyntaxError:
unterminated rege...
"one -1" makes Ruby
think you might be giving
an argument (of -1) to
method one. (Same for
+1 . . . or even *1!)
Again: us...
dbl = ->(x) { x * 2 }
=> #<Proc:... (lambda)>
dbl = ->x{ x * 2 }
=> #<Proc:... (lambda)>
dbl = -> x { x * 2 }
=> #<Proc:.....
class Foo
attr_reader :value
def initialize(v)
value = v
end
def set_val(v)
@value = v
end
end
f = Foo.new(3)
f.value
=> n...
What the fillintheblank? We didn't
change Parent’s @@value before
checking it, nor Child’s at all!
. . . Or did we?
@@ var...
class Parent
def initialize
puts "Parent init"
end
end
class NoInitChild < Parent
end
NoInitChild.new
Parent init
class No...
Superman vs. the Invisible Man
Child2.new.add 1, 2, 3, 5
ArgumentError: wrong
number of arguments (4
for 2)
Child2.new.add...
When will it end? (Or start?)
str = "OnenTwonThree"
str =~ /^Two$/
=> 4
str =~ /ATwoZ/
=> nil
str =~ /AOne/
=> 0
str =~ /T...
[].any?
=> false
[1].any?
=> true
[:foo, :bar].any?
=> true
# ok so far, BUT:
[nil].any?
=> false
[false].any?
=> false
[f...
Variables declared in blocks
passed to iterators (e.g.,
times or each) are undefined
at the top of each iteration!
Iterato...
arr = ["one", "two", "three"]
arr.freeze
arr << "four"
RuntimeError: can't modify
frozen Array
arr[0] = "eno"
RuntimeError...
Changing Fixnum to new
value means new object.
They can't be modified in
place! So, can’t modify a
frozen Array of Fixnums...
str = "foo"
str.upcase
=> ”FOO”
str
=> ”foo”
str.upcase!
=> ”FOO”
str
=> ”FOO”
# Now that it’s already FOO:
str.upcase!
=>...
Initial value given as
object is same object
for each slot (if modded
in place, not reassigned
as with = or +=).
Initial v...
Mostly same problem (and
solution) as Arrays.
WARNING: creates new
object on any access to
empty slot! May create
excessiv...
/* JAVA: */
try {
throw new MyException("blah");
} catch(MyException e) {
fix_it();
}
# RUBY:
index = catch(:idx) {
arr.ea...
- Watch out for these gotchas as you code.
- If Ruby behaves badly, refer to these slides.
- Available at http://bit.ly/Ru...
Add gotchas:
- to_s vs. to_str
- need to coordinate method_missing and respond_to_missing?
- rescue from a StandardError, ...
questions.any? ; gotchas[:more].any?
Contact information / shameless plug:
T.Rex-2015 [at] Codosaur [dot] us
+1-571-308-66...
Upcoming SlideShare
Loading in...5
×

Ruby Gotchas

282

Published on

We <3 .rb

but - still .rb < perfect

This presentation shows some of the ways that this language, engineered for maximum programmer happiness and least surprise, can still have some nasty gotchas.

Published in: Technology, Education
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
282
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
6
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Transcript of "Ruby Gotchas"

  1. 1. Ruby GotchasLast edited 2014-11-26 by Dave Aronson, T. Rex of Codosaurus, LLC
  2. 2. Though "engineered to maximize programmer happiness", with the "principle of least surprise", Ruby still has gotchas. This presentation will proceed from newbie trivial gotchas, to more advanced and confusing gotchas. We = 2 class Fixnum def rb; self; end end We <3 .rb => true But = 3 still = 1 perfect = 4 But - still .rb < perfect => true Ruby can be surprising!
  3. 3. Don't quote me on this, but . . . . x = 3 puts 'x = #{x}nx' x = #{x}nx puts "x = #{x}nx" x = 3 x String interpolation (including special chars like n) fails with 'single' quotes -- it requires "double" quotes. (Just like in most languages with string interpolation.) To avoid: use doubles whenever practical.
  4. 4. Only two things are false (falsey): false, and nil. Everything else is true (truthy), even 0 (false in C), "" (false in JS), [], etc. (Trips up people from C, JS, etc. where some of these are false.) It's twue! It's twue! true ? "true" : "false" => "true" false ? "true" : "false" => "false" nil ? "true" : "false" => "false" 1 ? "true" : "false" => "true" 0 ? "true" : "false" => "true" "false"? "true" : "false" => "true" "" ? "true" : "false" => "true" [] ? "true" : "false" => "true"
  5. 5. Symbols != strings. Even if same, printed. Remember which one to use for what (args). Ideally, take either: "Be liberal in what you accept, and conservative in what you send." (Postel's Law) str = "string" sym = :string puts str string puts sym string str == sym => false Hang him in effigy (String him up, symbolically)
  6. 6. bash> irb-1.9 str = "string" => "string" str[2] => "r" # makes sense *now* bash> irb-1.8 str = "string" => "string" str[2] => 114 # ??? ascii code! str[2..2] => "r" # that's better! str[2,1] => "r" # works too.... String... or nothing!
  7. 7. FOO = 5 => 5 FOO = 7 (irb):3: warning: already initialized constant FOO => 7 FOO => 7 Constants Aren't (Part 1/2) (Initial uppercase means constant, in Ruby.) Try to change a constant. Ooooh, you get a WARNING! BFD.
  8. 8. Even freezing doesn't work for Fixnums. It does work for arrays (sort of) and most other objects . . . he said foreshadowingly. FOO => 7 FOO.freeze => 7 FOO += 2 (irb):5: warning: already initialized constant FOO => 9 FOO => 9 Constants Aren't (Part 2/2)
  9. 9. Some are more equal than others Effectively: == is the usual (same value) .eql? is value and class (1 is Fixnum, 1.0 is Float) .equal? is same object It's actually much hairier; see docs on class Object 1 == 1.0 => true 1.eql? 1.0 => false a = "foo" b = "foo" a == b => true a.eql? b => true a.equal? b => false a.equal? a => true
  10. 10. Effectively: === is "case equality", as in case statements. A better name (IMHO) might be ".describes?", or overload ".includes?"! Again, it's actually much hairier; see the docs on class Object. Gets people from languages where === is identity, or same value and class. === != ==! 1 === 1 => true Fixnum === 1 => true 1 === Fixnum => false Class === Class Object === Object Class === Object Object === Class => all true Fixnum === Fixnum => false (1..3) === 2 => true 2 === (1..3) => false
  11. 11. && has higher precedence than =, so x = true && false means x = (true && false) and has lower precedence, so x = true and false means (x = true) and false Ruby Style Guide: Use && / || for boolean expressions, [use] and / or for control flow. x = true && false => false x => false # OK so far, but: x = true and false => false x => true Return value is false but variable is true! Why the mismatch?! and != &&
  12. 12. || has higher precedence than =, so x = false || true means x = (false || true) or has lower precedence so x = false or true means (x = false) or true Also, && is higher than ||, but and and or are equal, so they are evaluated left-to-right! x = false || true => true x => true # OK so far, but: x = false or true => true x => false Return value is true but variable is false! Why the mismatch?! or != ||
  13. 13. Whitespace-insensitive? NOT ALWAYS! With multiple args: - No parens, no problem. - Parens w/o space, OK. - Parens and space, NO! Parser thinks it's an expression, as one arg, but (1, 2) is not a valid Ruby expression! (All work fine w/ 1 arg.) def method(arg1, arg2); end method 1, 2 => nil method(1, 2) => nil method (1, 2) syntax error, unexpected ',', expecting ')' method (1, 2) ^ Don't be so sensitive! (Part 1/4)
  14. 14. def method; 42; end num = 21 method/num => 2 method / num => 2 method/ num => 2 method /num SyntaxError: unterminated regexp "method /num" is an unended regex or string! Ruby thinks you might be giving an argument to method meth. General principle: use BALANCED whitespace; both sides or neither. Don't be so sensitive! (Part 2/4)
  15. 15. "one -1" makes Ruby think you might be giving an argument (of -1) to method one. (Same for +1 . . . or even *1!) Again: use BALANCED whitespace; both sides or neither. def one 1 end one - 1 => 0 one-1 => 0 one- 1 => 0 one -1 ArgumentError: wrong number of arguments (1 for 0) Don't be so sensitive! (Part 3/4)
  16. 16. dbl = ->(x) { x * 2 } => #<Proc:... (lambda)> dbl = ->x{ x * 2 } => #<Proc:... (lambda)> dbl = -> x { x * 2 } => #<Proc:... (lambda)> two = -> { 2 } => #<Proc:... (lambda)> dbl = -> (x) { x * 2 } syntax error, unexpected tLPAREN_ARG, expecting keyword_do_LAMBDA or tLAMBEG two = -> () { 2 } same syntax error "Stabby" lambdas (1.9+) Parentheses optional Space before/after args without parens, OK. Space after parens, OK. Again, space before parens, NO! UPDATE: Fixed in 2.0! Don't be so sensitive! (Part 4/4)
  17. 17. class Foo attr_reader :value def initialize(v) value = v end def set_val(v) @value = v end end f = Foo.new(3) f.value => nil # not 3?! f.set_val 5 => 5 f.value => 5 'Ang onto yer @! Naked value becomes a temporary local variable! Solution: remember the @! (Or "self.".) Gets people from Java/C++, not so much Python (which needs "self." too). "You keep on using that variable. I don't think it means what you think it means."
  18. 18. What the fillintheblank? We didn't change Parent’s @@value before checking it, nor Child’s at all! . . . Or did we? @@ variables are shared with subclasses -- not just that they exist, but the variables themselves! Declaring Child’s @@value changed Parent’s, and inc’ing Parent’s changed Child’s. IMHO, best just forget them. class Parent @@value = 6 def self.value @@value end def self.inc_value @@value += 1 end end class Child < Parent @@value = 42 end Parent.value => 42 # wtf? Parent.inc_value Child.value => 43 # wtf?! Look out, it’s an @@!
  19. 19. class Parent def initialize puts "Parent init" end end class NoInitChild < Parent end NoInitChild.new Parent init class NormalChild < Parent def initialize puts "NormalChild init" end end NormalChild.new "NormalChild init" With init(ialize) or without it class SuperChild < Parent def initialize puts "SuperChild" super puts "init" end end SuperChild.new SuperChild Parent init init Parent's initialize runs automagically only if child has none. Else, parent's must be called to run.
  20. 20. Superman vs. the Invisible Man Child2.new.add 1, 2, 3, 5 ArgumentError: wrong number of arguments (4 for 2) Child2.new.add 1, 2 => 3 Child4.new.add 1, 2, 3, 5 => 11 super with no arg list sends what caller got super with explicit args sends those args to send NO args, use empty parens: super() class Parent def add *args args.inject :+ end end class Child2 < Parent def add arg1, arg2 super arg1, arg2 end end class Child4 < Parent def add a1, a2, a3, a4 super # no args! end end
  21. 21. When will it end? (Or start?) str = "OnenTwonThree" str =~ /^Two$/ => 4 str =~ /ATwoZ/ => nil str =~ /AOne/ => 0 str =~ /ThreeZ/ => 8 In "standard" regexps: ^ is start and $ is end... of the whole string. Ruby’s regexes default to multiline, so: ^ is start and $ is end... of any line! A is start and Z is end of the whole string. (Or z to include any newline… which is another gotcha!)
  22. 22. [].any? => false [1].any? => true [:foo, :bar].any? => true # ok so far, BUT: [nil].any? => false [false].any? => false [false, nil].any? => false .any? does not mean “any elements?”! With block: “do any make the block true?” Without: “are any truthy?” Has implicit block: { |element| element } getting .any?
  23. 23. Variables declared in blocks passed to iterators (e.g., times or each) are undefined at the top of each iteration! Iterators call the block repeatedly, so vars are out of scope again after each call. Built-in looping constructs (e. g., while or for) are OK. (Or declare vars before block.) 3.times do |loop_num| sum ||= 0 sum += 1 puts sum end 1 1 1 for loop_num in 1..3 sum ||= 0 sum += 1 puts sum end 1 2 3 (Un)Def Leppard
  24. 24. arr = ["one", "two", "three"] arr.freeze arr << "four" RuntimeError: can't modify frozen Array arr[0] = "eno" RuntimeError: can't modify frozen Array arr[0].object_id => 1234567890 arr[0].reverse! arr => ["eno", "two", "three"] arr[0].object_id => 1234567890 Freezing an array (or a hash) freezes it, not the items it contains. Strings can be modified in place. This way, you can modify a given slot in a frozen Array of Strings. Freeze (Ar)ray
  25. 25. Changing Fixnum to new value means new object. They can't be modified in place! So, can’t modify a frozen Array of Fixnums. (Fixnums and Integers have no bang-methods to demo trying with.) BTW: a Fixnum's object_id is value * 2 + 1. 1 is 1 … and ever more shall be so! arr = [1, 2, 3, 4] arr.freeze => [1, 2, 3, 4] arr << 5 RuntimeError: can't modify frozen Array arr[0] += 2 RuntimeError: can't modify frozen Array 1.object_id => 3 3.object_id => 7
  26. 26. str = "foo" str.upcase => ”FOO” str => ”foo” str.upcase! => ”FOO” str => ”FOO” # Now that it’s already FOO: str.upcase! => nil # ?! str => ”FOO” Well-known semi-gotcha: bang versions of methods are dangerous; usually may modify receiver. DO NOT RELY ON THEM RETURNING SAME VALUE AS NON-BANG VERSION! Many return nil if no change needed! (to! || ! to!) == ?
  27. 27. Initial value given as object is same object for each slot (if modded in place, not reassigned as with = or +=). Initial value given as block gets evaluated separately for each slot. Use this to create new vars for each. An Array of New Gotchas class Person attr_accessor :name end people = Array.new(3, Person.new) people[0].name = "Alice" people[1].name = "Bob" people[0].name => "Bob" # should have been "Alice"! people = Array.new(3) { Person.new } people[0].name = "Alice" people[1].name = "Bob" people[0].name => "Alice"
  28. 28. Mostly same problem (and solution) as Arrays. WARNING: creates new object on any access to empty slot! May create excessive number of new objects; ruins checking “real” contents or count (nil-checking, .size, etc.). langs = Hash.new [] langs[:jane] << "Java" langs[:rachel] << "Ruby" langs[:jane] => ["Java", "Ruby"] langs[:rachel] => ["Java", "Ruby"] langs = Hash.new { |h, k| h[k] = [] } langs[:jane] << "Java" langs[:rachel] << "Ruby" langs[:jane] => ["Java"] langs[:rachel] => ["Ruby"] Making a Hash of it
  29. 29. /* JAVA: */ try { throw new MyException("blah"); } catch(MyException e) { fix_it(); } # RUBY: index = catch(:idx) { arr.each_with_index do |v, i| throw :idx, i if v == target end -1 } begin raise MyException.new "blah" rescue MyException => e fix_it end Rescue Me, Throw a Line, I'll Try to Catch It! In Ruby, throw and catch are NOT for exceptions! They are advanced flow control, to exit deep nesting. Ruby uses raise and rescue for exceptions.
  30. 30. - Watch out for these gotchas as you code. - If Ruby behaves badly, refer to these slides. - Available at http://bit.ly/RubyGotchas - If your gotcha isn’t listed, tell me; maybe I’ll add it! I’m Gonna Getcha Getcha Getcha Getcha!
  31. 31. Add gotchas: - to_s vs. to_str - need to coordinate method_missing and respond_to_missing? - rescue from a StandardError, not an Exception - instance_eval with calls in local scope - private data isn’t really, and not at all w/ class methods - private/protected not same as in other languages - diffs in lambda/proc/block/method, WRT break/next/return/etc. - braces vs. do-end (TL;DR: braces high precedence, do-end low) - attribute=(val) always returns the argument, no matter the code - Proxies are always truthy, even if the target is not - class Foo::Bar, defined outside Module Foo, won’t see inside Foo - in debugging, “next” goes into loops but skips over blocks - vars introduced in a loop (not block!) are visible outside it (?) - private methods are accessible by the instance, not the whole class Rails gotchas? Screencast series -- maybe text, audio, and screencast versions? The Someday-Maybe List
  32. 32. questions.any? ; gotchas[:more].any? Contact information / shameless plug: T.Rex-2015 [at] Codosaur [dot] us +1-571-308-6622 www.Codosaur.us (Codosaurus, LLC main site) www.linkedin.com/profile/view?id=719998 Blog.Codosaur.us (code blog) www.Dare2XL.com (excellence blog) @davearonson AVAILABLE FOR CONSULTING! Questions/Contact/Etc.
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×