Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
tech addict - ruby gotchas and caveats
[go: Go Back, main page]

 

ruby gotchas and caveats

December 14th, 2006

While learning ruby I've run into a few gotchas that I think may be worth discussion/sharing. It's worth noting that I'm coming from a Java,C#,C based background. These may not seem that odd to people coming from different backgrounds.
  1. strings aren't auto converted into numbers or strings
    1
    2
    3
    4
    
    irb(main):001:0> "1" + 1
    TypeError: can't convert Fixnum into String
            from (irb):1:in `+'
            from (irb):1


  2. false and nil are the only valid false values, anything else is true
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    irb(main):001:0> p "rats" if 1 == true
    => nil
    irb(main):002:0> p "rats" if 0 == false
    => nil
    irb(main):003:0> p "yay?" if nil
    => nil
    irb(main):004:0> p "oro?" if 6
    "oro?"
    => nil
    irb(main):005:0> p "oro?" if -6
    "oro?\n"
    => nil
    irb(main):005:0> p "will print" if 0
    "oro?\n"
    => nil


  3. && and || are the high priority AND/OR constructs, and and or are low priority versions
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    irb(main):016:0> x = nil or 99
    => 99
    irb(main):017:0> x
    => nil
    irb(main):018:0> x = 42 and 99
    => 99
    irb(main):019:0> x
    => 42
    irb(main):020:0> x = nil || 99
    => 99
    irb(main):021:0> x
    => 99
    irb(main):022:0> x = 49 && 100
    => 100
    That last one came as a surprise to me, as I expected it to return true. Guess not.

  4. Exceptions and raise vs catch

    catch/throw are not the same as raise/rescue. catch/throw allows you to quickly exit blocks back to a point where a catch is defined for a specific symbol, raise rescue is the real exception handling stuff involving the Exception object. If your exception doesn't inherit from StandardError it will NOT be caught by default!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    
    irb(main):001:0> class MyException < Exception
    irb(main):002:1>   end
    => nil
    irb(main):003:0> def exception_miss
    irb(main):004:1>   raise MyException.new
    irb(main):005:1>   rescue
    irb(main):006:1>   p "saved!"
    irb(main):007:1>   end
    => nil
    irb(main):008:0> begin
    irb(main):009:1*     exception_miss
    irb(main):010:1>   rescue Exception
    irb(main):011:1>   p "not so much"
    irb(main):012:1>   end
    "not so much"
    => nil
    irb(main):015:0> def exception_hit
    irb(main):016:1>   raise NewException
    irb(main):017:1>   rescue
    irb(main):018:1>   p "excellent!"
    irb(main):019:1>   end
    => nil
    irb(main):020:0> exception_hit
    "excellent!"
    => nil
    irb(main):021:0> def catcher
    irb(main):022:1>   throw :testing
    irb(main):023:1>   rescue
    irb(main):024:1>   p "gotcha?"
    irb(main):025:1>   end
    => nil
    irb(main):026:0> catcher
    "gotcha?"
    => nil
    irb(main):071:0> def throw_no_rescue
    irb(main):072:1>   throw :gotme
    irb(main):073:1>   end
    => nil
    irb(main):074:0> def fire
    irb(main):075:1>   catch(:gotme) do
    irb(main):076:2*       p "start"
    irb(main):077:2>     throw_no_rescue
    irb(main):078:2>     p "after throw"
    irb(main):079:2>     end
    irb(main):080:1>   p "after catch"
    irb(main):081:1>   end
    => nil
    irb(main):082:0> throw_no_rescue
    NameError: uncaught throw `gotme'
            from (irb):72:in `throw'
            from (irb):72:in `throw_no_rescue'
            from (irb):82
            from :0
    irb(main):083:0> fire
    "start"
    "after catch"
    => nil


  5. no class/module unloading

  6. no native unicode (ruby < 2.0)

  7. private never truely private
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
    irb(main):001:0> class MonkeyNinja
    irb(main):002:1>   private
    irb(main):003:1>   def secret_weakness
    irb(main):004:2>     p "eep! dead monkey!"
    irb(main):005:2>     end
    irb(main):006:1>   end
    => nil
    irb(main):007:0> m = MonkeyNinja.new
    => #<MonkeyNinja:0x2c01d5c>
    
    irb(main):008:0> m.methods
    => [...no secret weakness! ]
    
    irb(main):009:0> m.private_methods
    => [stuff, "secret_weakness", "lambda"]
    irb(main):010:0> m.send :secret_weakness
    "eep! dead monkey!"
    => nil
    
    irb(main):011:0> m.secret_weakness
    NoMethodError: private method 'secret_weakness' called for #<MonkeyNinja:0x2c01d5c>
            from (irb):10
            from :0
    
    irb(main):012:0> m.instance_eval do
    irb(main):013:1*     secret_weakness
    irb(main):014:1>   end
    "eep! dead monkey!"
    => nil
    
    irb(main):015:0> def m.kill_you
    irb(main):016:1>   secret_weakness
    irb(main):017:1>   end
    => nil
    irb(main):018:0> m.kill_you
    "eep! dead monkey!"
    => nil


  8. 1/2 != 0.5 unless require 'mathn' (this is the default in many languages anyway, sometimes integer math is your friend). With mathn 1/2 becomes a different type (Rational) than 0.5 (Float).
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    irb(main):010:0> 1.0/2
    => 0.5
    irb(main):011:0> 1/2
    => 0
    irb(main):012:0> p "normalcy" if 1/2 == 0.5
    => nil
    irb(main):013:0> require 'mathn'
    => true
    irb(main):014:0> 1/2
    => 1/2
    irb(main):015:0> p "normalcy" if 1/2 == 0.5
    "normalcy"
    => nil
    irb(main):016:0> a = 0.5
    => 0.5
    irb(main):017:0> a.class
    => Float
    irb(main):018:0> b = 1/2
    => 1/2
    irb(main):019:0> b.class
    => Rational


  9. class variables and their inheritance semantics
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    irb(main):001:0> class OmgFun
    irb(main):002:1>   @@taco = "testing"
    irb(main):003:1>   def taco
    irb(main):004:2>     @@taco = "me"
    irb(main):005:2>     end
    irb(main):006:1>   def self.to_s
    irb(main):007:2>     @@taco
    irb(main):008:2>     end
    irb(main):009:1>   end
    => nil
    irb(main):010:0> class B < OmgFun
    irb(main):011:1>   @@taco = "B's taco"
    irb(main):012:1>   end
    => "B's taco"
    irb(main):013:0> class C < B
    irb(main):014:1>   @@taco = "C's taco"
    irb(main):015:1>   end
    => "C's taco"
    irb(main):016:0> C.to_s
    => "C's taco"
    irb(main):017:0> B.to_s
    => "C's taco"
    irb(main):018:0> OmgFun.to_s
    => "C's taco"


  10. singleton class modification for the class objects isn't good or really allowed
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    irb(main):001:0> class Daffy
    irb(main):002:1>   @@mine = "all mine"
    irb(main):003:1>   def to_s
    irb(main):004:2>     @@mine
    irb(main):005:2>     end
    irb(main):006:1>   end
    => nil
    irb(main):007:0> class << Daffy
    irb(main):008:1>   def jigga
    irb(main):009:2>     @@mine = "bunny time"
    irb(main):010:2>     end
    irb(main):011:1>   end
    => nil
    irb(main):012:0> d = Daffy.new
    => all mine
    irb(main):013:0> Daffy.jigga
    (irb):9: warning: class variable access from toplevel singleton method
    => "bunny time"
    irb(main):014:0> d
    => all mine
    irb(main):015:0> d = Daffy.new
    => all mine
    irb(main):016:0> def Daffy.wha
    irb(main):017:1>   @@mine = "wabbit"
    irb(main):018:1>   end
    => nil
    irb(main):019:0> Daffy.wha
    => "wabbit"
    irb(main):020:0> d
    => all mine
    irb(main):021:0> d = Daffy.new
    => all mine

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
    irb(main):001:0> class A
    irb(main):002:1>   @@foo = "bar"
    irb(main):003:1>   def self.print_foo
    irb(main):004:2>     p @@foo
    irb(main):005:2>     end
    irb(main):006:1>   end
    => nil
    irb(main):007:0> A.print_foo
    "bar"
    => nil
    irb(main):008:0> A.class_eval do
    irb(main):009:1*     @@foo = "no go"
    irb(main):010:1>   end
    => "no go"
    irb(main):011:0> A.print_foo
    "bar"
    => nil
    irb(main):012:0> A.class_eval do
    irb(main):013:1*     def self.new_hotness
    irb(main):014:2>     p @@foo
    irb(main):015:2>     end
    irb(main):016:1>   end
    => nil
    irb(main):017:0> A.new
    A.new          A.new_hotness
    irb(main):017:0> A.new
    A.new          A.new_hotness
    irb(main):017:0> A.new_hotness
    "no go"
    => nil
    irb(main):018:0> A.instance_eval do
    irb(main):019:1*     p @@foo
    irb(main):020:1>   end
    "no go"
    => nil
    irb(main):021:0> A.module_eval do
    irb(main):022:1*     p @@foo
    irb(main):023:1>   end
    "no go"
    => nil
    irb(main):024:0> A.print_foo
    "bar"
    => nil


  11. assignment methods always return the assigned value, not the return value of the assignment function
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    irb(main):001:0> class Foo
    irb(main):002:1>   def self.bar=(val)
    irb(main):003:2>     return "apeiros!"
    irb(main):004:2>     end
    irb(main):005:1>   end
    => nil
    irb(main):006:0> Foo.bar = "meep"
    => "meep"
    irb(main):007:0> Foo.bar
    NoMethodError: undefined method 'bar' for Foo:Class
            from (irb):7
            from :0


  12. local variable scoping is tricky and can hide other variables and methods (this is slated for a change in ruby 2.0)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    irb(main):008:0> class Pizza
    irb(main):009:1>   def my_hut
    irb(main):010:2>     p "tasty!"
    irb(main):011:2>     end
    irb(main):012:1>   def breadstick
    irb(main):013:2>     if nil
    irb(main):014:3>       my_hut = "never executed"
    irb(main):015:3>       end
    irb(main):016:2>     my_hut
    irb(main):017:2>     end
    irb(main):018:1>   end
    => nil
    irb(main):019:0> m = Pizza.new
    => #<Pizza:0x2c63cc8>
    irb(main):020:0> m.breadstick
    => nil


  13. Another class variable trouble spot. You might think that class_eval would provide access to the class's context and variables, apparently not so much. This example/issue came from Gary at a New Haven Ruby Brigade meeting.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
    irb(main):001:0> class A
    irb(main):002:1>   @@avar = "hello"
    irb(main):003:1>   end
    => "hello"
    irb(main):004:0> A.class_variables
    => ["@@avar"]
    irb(main):005:0> A.class_eval { puts @@avar }
    NameError: uninitialized class variable @@avar in Object
            from (irb):5
            from (irb):5:in 'class_eval'
            from (irb):5
            from :0
    irb(main):006:0> A.instance_eval { puts @@avar }
    NameError: uninitialized class variable @@avar in Object
            from (irb):6
            from (irb):6:in 'instance_eval'
            from (irb):6
            from :0
    irb(main):007:0> A.module_eval { puts @@avar }
    NameError: uninitialized class variable @@avar in Object
            from (irb):7
            from (irb):7:in 'module_eval'
            from (irb):7
            from :0
    irb(main):008:0> class A
    irb(main):009:1>   def my_avar
    irb(main):010:2>     @@avar
    irb(main):011:2>     end
    irb(main):012:1>   end
    => nil
    irb(main):013:0> a = A.new
    => #<A:0x2c27890>
    irb(main):014:0> a.my_avar
    => "hello"
    irb(main):015:0> a.instance_eval { puts @@avar }
    NameError: uninitialized class variable @@avar in Object
            from (irb):15
            from (irb):15:in 'instance_eval'
            from (irb):15
            from :0


19 Responses to “ruby gotchas and caveats”

  1. Liquid error: undefined method `request' for nil:NilClass C. Reb Says:
    if 1 == true if 0 == false ...wouldn't have worked anyway. equality between incongruous types is *always* false
  2. Liquid error: undefined method `request' for nil:NilClass segfault Says:
    In ruby that is true, but not in some other languages (C, C++ and php off the top of my head). So I think it's safe to say that this could be considered a gotcha if one was coming from another language.
  3. Liquid error: undefined method `request' for nil:NilClass anon Says:
    >> strings aren't auto converted into numbers or strings That is not a caveat, languages should behave this way. The caveat is in languages like Javascript and PHP.
  4. Liquid error: undefined method `request' for nil:NilClass davidg Says:
    i use php daily at work. But at school I have used Ruby, Java, Ada95 and all of these will say "1" == 1 is false. This is true for most languages that dont coeherce types at runtime. Most of your points are well noted though.
  5. Liquid error: undefined method `request' for nil:NilClass segfault Says:
    anon: Java, C# also auto-convert when faced with "A" + 1. Perl seems to do something funky but not throw an error/exception (perl monks pls correct me if I'm wrong). Python joins ruby in the exception throwing camp.
  6. Liquid error: undefined method `request' for nil:NilClass David Black Says:
    Hi -- I'm not sure about #10. It seems to be more about class variables (which always seem to bring trouble) than singleton classes of class objects. Singleton classes of class objects actually tend to be modified more than any other singleton classes, since they are where class methods are stored.
  7. Liquid error: undefined method `request' for nil:NilClass Derek Says:
    This post is great. Ruby's purposeful denial of encapsulation can be frustrating, and often leads to some too-clever-for-its-own-good code. This is a great round-up of tricky aspects. That being said, I disagree about #12. Scope is a fairly straight-forward subject, particularly when it's laid out in the fashion you describe. Unless you provide a better example, it's just not tricky at all. You might be pointing out that there's no interpreter complaint when referencing an unassigned variable, but that isn't a scope issue, just a gotcha all in itself (from a static language viewpoint). Thanks for the great post and keep churning them out!
  8. Liquid error: undefined method `request' for nil:NilClass Gregory Brown Says:
    David Black makes a good point about 10. It's actually a class variable issue. Usually, class instance variables are more appropriate, anyway.
  9. Liquid error: undefined method `request' for nil:NilClass segfault Says:
    David & Greg, my main question is why the difference in object point of view regarding singletons and class variables. I would think that the class variable references within the singleton would map over to the original class variables. Any pointers about this are appreciated, like David said, class variables seem to be a source of great confusion.
  10. Liquid error: undefined method `request' for nil:NilClass phaylon Says:
    segfault, unfortunately you didn't say what you find "funky" in Perl's behavior. For me, it just coerces: perl -wle'print "2" * 2' returning 4
  11. Liquid error: undefined method `request' for nil:NilClass segfault Says:
    phaylon, I originally tested with "test: " + 2. Which just printed 2. I didn't test "2" + 1, which prints 3. This does make sense to me now.
  12. Liquid error: undefined method `request' for nil:NilClass Gregory Brown Says:
    segy, I usually avoid class variables altogether. Is this more what you would expect? http://pastie.caboo.se/28770
  13. Liquid error: undefined method `request' for nil:NilClass segfault Says:
    Greg, Yea, thats a lot closer to what I would expect. So when i want something that would be static in java/c#, I should be using an instance variable in a class/singleton context..?
  14. Liquid error: undefined method `request' for nil:NilClass Robert Jones Says:
    #7 is most interesting. I saw a plea, or demand, on some Rails blog to "stop using variables you don't own!". Basically they wanted to change core rails code or some such, but difficult when you've got alot of folks depending on version 1 let's say, and they, like any smart users, have made the most of the published interface. James Duncan Davidson gave a great talk on bulletproofing your interfaces (learned the hard way). Now in some languages, this is built-in, but it seems with Ruby it almost works against you, and you have to go out of your way to provide standard Object Oriented Design concepts like encapsulation. Please enlighten me on the philosophy here: OO stuff gets in the way sometimes? I'm all for scripting languages, dynamic stuff & the like, have lots of fun with awk & perl, but certainly issues like these seem to me to dictate that Ruby will have it's limitations in capturing more than a limited niche. Am I wrong on this? Provide references/examples if possible.
  15. Liquid error: undefined method `request' for nil:NilClass segfault Says:
    Robert, it's worth noting that both Java and C# (all .net languages afaik) allow programmers to muck with private/protected data and methods using reflection. I'm of the opinion that this is useful, but as with any power tool serious care should be taken when using it. In an older post I wrote about some problems I encountered using feedtools that stemmed from mongrel monkey patching kernel. Anytime you monkey with the internals of someone else's code, your risking side-effects.
  16. Liquid error: undefined method `request' for nil:NilClass Derek Says:
    After rereading #12, I completely take back what I said about scope not being tricky earlier. Good bit of information to know. I'm glad I haven't run across that little "feature" in any of my code.
  17. Liquid error: undefined method `request' for nil:NilClass phaylon Says:
    segfault, Yes, everything that doesn't look like a number for perl will be evaluated as 0 on coercion. Although if you activate warnings (which should be done always, and only skipped with good reasons) you'll get a "Argument "foo" isn't numeric in multiplication (*) at..." warning.
  18. Liquid error: undefined method `request' for nil:NilClass Justin Says:
    I just wanted to post about #11. Initially, that surprised me and seemed weird too. After some though, though, I realized it's actually a clever trick. It allows you to chain assignments. If object 'obj' has methods 'a=', 'b=', and 'c=', you can write code like this: obj.a = obj.b = obj.c = "hello" If the methods returned values as normal, then the programmer has to be sure to return the value assigned, rather than some arbitrary value, every time. With the choice Ruby made here, "hello" will be propagated to each assignment method, which is certainly not surprising.
  19. Liquid error: undefined method `request' for nil:NilClass segfault Says:
    Justin, offhand I would expect a call to obj.c after the call to obj.c=, to provide a value to obj.b=. It's nice that this functionality exists. When I think "what is the interpreter doing?", passing along the input value as the output is not the first thing that comes to mind. Might just be me though.

Leave a Reply