Ruby: A Python Programmer's Perspective Part III
This is a somewhat random list of things that were interesting or surprising to me when I read Ruby for Rails. The previous post in the series is Ruby: A Python Programmer's Perspective Part II.
I've mentioned before that Ruby has symbols which are similar to, but distinct from, strings (which are mutable). The syntax is :foo. To create a symbol with spaces, use :"foo bar".
Ruby has two syntax for ranges. ".." includes both endpoints. "..." does not include the right endpoint and thus behaves like slices in Python do:
Ruby has "==", "===", ".eql", and ".equal". "===" is for case statements. "==" is related to ">=", etc. ".eql" tests that the two objects have the "same content". Usually, "==" and ".eql" return the same thing, but their implementation is different. ".equal" is reserved for object identity. Hence:
When you are appending one string onto the end of another string, using += does not modify the original string, whereas << does:
I was a little surprised that you could connect two statements with a semicolon inside parenthesis:
If you want to treat a string as an array of characters, use '"foobar".split(//)'.
String#scan is a pretty cool method that repeatedly tries to match a regular expression while iterating over a string. Unlike #match, it returns all of the matches, instead of just the first:
I've mentioned before that Ruby has symbols which are similar to, but distinct from, strings (which are mutable). The syntax is :foo. To create a symbol with spaces, use :"foo bar".
Ruby has two syntax for ranges. ".." includes both endpoints. "..." does not include the right endpoint and thus behaves like slices in Python do:
>> (0...2).each {|x| p x}Since the "+" operator is just syntactic sugar for the "+" method, the following are equivalent:
0
1
=> 0...2
>> (0..2).each {|x| p x}
0
1
2
=> 0..2
>> 1 + 1Whenever possible, I think interpreters should raise an exception if you misspell something. Hence, I found the following interesting:
=> 2
>> 1.+(1)
=> 2
>> def fnil and false are the only two objects that evaluate to false:
>> p @undefed_instance_variable # This prints nil.
>> p undefined_local # This raises a NameError.
>> end
=> nil
>> f
nil
NameError: undefined local variable or method `undefined_local' for main:Object
from (irb):39:in `f'
from (irb):41
>> if []: puts "Still true"; endIn Python, "truthiness" is a lot more complex. For instance, [1] is true, whereas [] is false, and in general, classes can define their own notion of truthiness.
Still true
=> nil
Ruby has "==", "===", ".eql", and ".equal". "===" is for case statements. "==" is related to ">=", etc. ".eql" tests that the two objects have the "same content". Usually, "==" and ".eql" return the same thing, but their implementation is different. ".equal" is reserved for object identity. Hence:
>> "a" == "a"If you want to see all of a class's instance methods, use the "instance_methods" method. If you don't want to see inherited methods, pass false to "instance_methods":
=> true
>> "a".eql?("a")
=> true
>> "a".equal?("a")
=> false
>> "a" === "a"
=> true
>> class CUsing Class.instance_methods.sort is helpful too.
>> def f
>> end
>> end
=> nil
>>
?> C.instance_methods
=> ["inspect", "f", "clone", "method", "public_methods",
"instance_variable_defined?", "equal?", "freeze", "methods", "respond_to?",
"dup", "instance_variables", "__id__", "object_id", "eql?", "require", "id",
"singleton_methods", "send", "taint", "frozen?", "instance_variable_get",
"__send__", "instance_of?", "to_a", "type", "protected_methods",
"instance_eval", "==", "display", "===", "instance_variable_set", "kind_of?",
"extend", "to_s", "hash", "class", "tainted?", "=~", "private_methods", "gem",
"nil?", "untaint", "is_a?"]
>>
?> C.instance_methods(false)
=> ["f"]
When you are appending one string onto the end of another string, using += does not modify the original string, whereas << does:
>> s = "foo"Indexing a string returns that character's ASCII value, which is a bit surprising. Slicing does what you would expect:
=> "foo"
>> t = s
=> "foo"
>> s += "bar"
=> "foobar"
>> s
=> "foobar"
>> t
=> "foo"
>> u = t
=> "foo"
>> u << "bar"
=> "foobar"
>> u
=> "foobar"
>> t
=> "foobar"
=> "abc"However, indexing a string to set a character works as expected:
>> s[1]
=> 98
>> s[1,1]
=> "b"
>> s[1] = 'B'Arrays have a "concat" method. "concat" and "+" operate as you might expect. "concat" is conceptually "concat!" because it modifies the array, but it's spelled without the "!":
=> "B"
>> s
=> "aBc"
>> s = [1, 2]To get both the values and indexes of an array as you loop over it, use the each_with_index method:
=> [1, 2]
>> t = [3, 4]
=> [3, 4]
>> s + t
=> [1, 2, 3, 4]
>> s
=> [1, 2]
>> s.concat(t)
=> [1, 2, 3, 4]
>> s
=> [1, 2, 3, 4]
>> t
=> [3, 4]
>> ['a', 'b', 'c'].each_with_index {|x, i| puts "#{i} => #{x}"}There are a couple ways to create a hash. The second syntax is a bit weird to my eyes:
0 => a
1 => b
2 => c
>> {:a => :b}By default, you'll get nil if you try to lookup a key that doesn't exist in a hash. As I've mentioned before, I prefer to get exceptions by default if I misspell things. Fortunately, the fetch method exists:
=> {:a=>:b}
>> Hash[:a => :b]
=> {:a=>:b}
>> h = {:a => :b}The default nil behavior of Ruby hashes is particular frustrating to me since Ruby uses hashes for keyword arguments. It's really easy for a misspelled option to lead to a silent bug:
=> {:a=>:b}
>> h[:c]
=> nil
>> h.fetch(:c)
IndexError: key not found
from (irb):50:in `fetch'
from (irb):50
>> def f(arg, options)I think real support for keyword arguments is better. Here's the Python:
>> p arg
>> if options[:excited]
>> puts "Oh, yeah!" # This doesn't get printed.
>> end
>> end
=> nil
>>
?> f("hello", :excitid => true) # Oops, typo.
"hello"
=> nil
>>> def f(arg, excited=False):You can still use the Ruby approach in Python using the "**kargs" syntax, but it's not the default.
... print arg
... if excited:
... print "Yeah!"
...
>>> f("hello", excitid=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'excitid'
I was a little surprised that you could connect two statements with a semicolon inside parenthesis:
>> (puts 'hi'; puts 'there') unless 100 < 10Iterating over a string yields lines, not characters like it does in Python:
hi
there
=> nil
>> "foo\nbar".each {|line| puts line}When using regular expressions, the =~ operator returns the index of the match, whereas the #match method returns a MatchData object:
foo
bar
=> "foo\nbar"
>> /o/ =~ 'foo'Be careful. The MatchData object stores captures starting at 1, whereas the #captures method stores captures starting at 0:
=> 1
>> /o/.match('foo')
=> #<MatchData:0x35d5cc>
>> match = /(o)/.match('foo')See my earlier post concerning "^" vs. "\A" in regular expressions. Ruby and Python are subtly different. Whereas I would normally use "^" in Python, to get the same behavior, I would use "\A" in Ruby.
=> #<MatchData:0x357654>
>> match[1]
=> "o"
>> match.captures[0]
=> "o"
If you want to treat a string as an array of characters, use '"foobar".split(//)'.
String#scan is a pretty cool method that repeatedly tries to match a regular expression while iterating over a string. Unlike #match, it returns all of the matches, instead of just the first:
>> s = "John Doe was the father of Jane Doe."Okay, that's it for this post, but I've got more coming :)
=> "John Doe was the father of Jane Doe."
>> s.scan(/([A-Z]\w+)\s+([A-Z]\w+)/)
=> [["John", "Doe"], ["Jane", "Doe"]]
Comments
http://jjinux.blogspot.com/2009/02/ruby-python-programmers-perspective_9838.html