Saturday, 25 May 2013

Ruby: stream-of-consciousness

G'day:
OK, so I'm fiddling around with Ruby (as per my previous post), and am typing this article as I explore.

Prior to starting this I googled "ruby IDE", and saw a lot of advice from people to just use a text editor. There are always people who claim they do all their code in notepad or vi etc, and I always figure they're just trying to sound cool. Whereas I think they just sound stupid, because whilst anyone can type code into a blank, featureless typing window (and we've all had to do emergency hacks on prod servers using vi or notepad, so it's good to not have to rely on code assist, etc), it's not exactly the most professionally productive way of going about one's business.

It seems like the general opinion is to use the plug-in for NetBeans, or a plug-in for Aptana. The plug-in for NetBeans has been discontinued (although someone's blogged how to install it anyway... I might need to look at that later on), so I have installed Aptana as a plug-in to Eclipse, adjacent to my ColdFusion Builder plug-in. This seems to have had the effect of slowing down Eclipse even more, and interfering with my Eclipse config, so this has not impressed me one bit.

One of the Aptana pages I landed on suggested install Ruby via RubyInstaller, which I duly have done, and that all seems to work, which is cool.

I dunno how to do all the IDE-y stuff yet, but I can create a Hello World file and run it from the command line.

Very simple version:

# helloWorld.rb
puts "Hello World"

OO version:

# helloWorldOO.rb
class HelloWorld
   def initialize(name)
      @name = name.capitalize
   end
   def sayHi
      puts "Hello #{@name}!"
   end
end

hello = HelloWorld.new("World")
hello.sayHi

And at the command line:

C:\apps\Ruby200-x64\bin>ruby C:\src\ruby\misc\helloWorld.rb
Hello World

C:\apps\Ruby200-x64\bin>ruby C:\src\ruby\misc\helloWorldOO.rb
Hello World!

C:\apps\Ruby200-x64\bin>

Cool. I'll not try to explain what's going on with that code, as I have no idea. But it's pretty self-explanatory at this level.

Another thing I noticed is that Ruby has a CLI, which one can run using irb:

C:\apps\Ruby200-x64\bin>irb
DL is deprecated, please use Fiddle
irb(main):001:0> puts "Hello World"
Hello World
=> nil
irb(main):002:0>

NB: I am also not vouching for any "best practices" here... I'm just telling you what I'm messing around with.



On the Code School website, I've started the reallyreally beginner "class", which has got me typing stuff into a web-based command interpreter. I'm currently keying in stuff like "4+4" and "Adam" and stuff. Type an expression in, and Ruby evaluates it. I can do the same in my irb window, eg:

irb(main):010:0> 3.14 + 42
=> 45.14
irb(main):011:0>

Ruby seems to be one of these languages in which everything is an object, so the tutorial has had me do this sort of thing:

"Zachary".reverse
(yields "yrahcaZ")

"Zachary".length
(yields 7)

Note how method calls don't need parentheses.

I can also do this, which is neat:

"Zachary" * 5
(yields "ZacharyZacharyZacharyZacharyZachary")

So if one multiplies a string, one gets the string that many times. I tried this:

"Zachary" + 2

And got this:

=> true
irb(main):015:0> "Zachary" +2
TypeError: no implicit conversion of Fixnum into String
        from (irb):15:in `+'
        from (irb):15
        from C:/apps/Ruby200-x64/bin/irb:12:in `<main>'
irb(main):016:0>

Yeah... fair enough ;-)

Continuing with the tutorial, they demonstrate the inverse of me trying to add 2 to a string, by calling reverse on a number (which errors), and then showing how to convert the number to a string, and then it's all good:

irb(main):017:0> 40.reverse
NoMethodError: undefined method `reverse' for 40:Fixnum
        from (irb):17
        from C:/apps/Ruby200-x64/bin/irb:12:in `<main>'
irb(main):018:0> 40.to_s.reverse
=> "04"
irb(main):019:0>

Hmmmm... method names with underscores in them. I don't like that much. Oh well.

The tutorial observes there's a few conversion methods:


Oh... I didn't even know which version of Ruby I was running (OK, I should have guessed from the install path: C:\apps\Ruby200-x64), so googled a way to find out. And found this page. One can do stuff like this:

irb(main):023:0> RUBY_VERSION
=> "2.0.0"
irb(main):024:0> RUBY_PLATFORM
=> "x64-mingw32"
irb(main):025:0> RUBY_RELEASE_DATE
=> "2013-05-14"
irb(main):026:0>

So v2.0.0 it is.

The tutorial continues to explain arrays, and the syntax is familiar:

["tahi", "rua", "toru", "wha"]

And one can call methods directly on the literal array:


["tahi", "rua", "toru", "wha"].length

(yields 4).

Variable assignment is the same as in CFML:

numbers = ["tahi", "rua", "toru", "wha"]

And they observe arrays have a sort method:

numbers.sort

(yields ["rua", "toru", "tahi", "wha"])

There's two different approaches to sorting available. the sort method sorts the input array, returning a new array. the sort! (with the !) sorts the array itself, returning it. EG:

<["whero", "karaka", "kowhai", "kakariki", "kikorangi", "tuauri", "tawatawa"]
=> ["whero", "karaka", "kowhai", "kakariki", "kikorangi", "tuauri", "tawatawa"]
irb(main):036:0> rainbow.sort
=> ["kakariki", "karaka", "kikorangi", "kowhai", "tawatawa", "tuauri", "whero"]
irb(main):037:0> rainbow
=> ["whero", "karaka", "kowhai", "kakariki", "kikorangi", "tuauri", "tawatawa"]
irb(main):038:0> rainbow.sort!
=> ["kakariki", "karaka", "kikorangi", "kowhai", "tawatawa", "tuauri", "whero"]
irb(main):039:0> rainbow
=> ["kakariki", "karaka", "kikorangi", "kowhai", "tawatawa", "tuauri", "whero"]
irb(main):040:0>

Notice how sort doesn't change numbers, but sort! does.

One interesting thing I have noticed. all this code is coming pretty naturally. With the PHP stuff I've been doing recently, I've needed to refer to the docs constantly to remind me what I'm supposed to type in. With this Ruby stuff, it's so "obvious" that it's sticking first time. That said... it's not like I'm doing anything complicated yet.

The next step of the tutorial is a bit gimmicky, but I'll demonstrate it anyhow. Typing this all in line by line in the CLI is a bit messy, so I'll use a file for this. Here we take the first verse of NZ's national anthem, and switch it around, line by line:

# gdnz.rb
anthem = "
E Ihowa Atua,
O nga iwi matou ra
Ata whakarangona;
Me aroha noa
Kia hua ko te pai;
Kia tau to atawhai;
Manaakitia mai
Aotearoa
"
puts anthem

puts "\n"

lines = anthem.lines
reversedLines = lines.to_a.reverse
reversedAnthem = reversedLines.join

puts reversedAnthem

This outputs (the first one is the correct version, the second is the reversed version):

E Ihowa Atua,
O nga iwi matou ra
Ata whakarangona;
Me aroha noa
Kia hua ko te pai;
Kia tau to atawhai;
Manaakitia mai
Aotearoa

Aotearoa
Manaakitia mai
Kia tau to atawhai;
Kia hua ko te pai;
Me aroha noa
Ata whakarangona;
O nga iwi matou ra
E Ihowa Atua,


Of course one could do all this in one statement:

# gdnz2.rb
puts "
E Ihowa Atua,
O nga iwi matou ra
Ata whakarangona;
Me aroha noa
Kia hua ko te pai;
Kia tau to atawhai;
Manaakitia mai
Aotearoa
"
.lines
.to_a
.reverse
.join

Interestingly, I've made a point of spreading all this over multiple lines, and Ruby works out what I mean no problems. That's quite neat.

There's a coupla new methods in there, the doc links for which are as follows:
On the summary page there's an observation that methods can have punctuation in their names (eg: sort!)... that's good clarification: I thought the ! was a modifier, but it's actually part of the method name. Another example given is the include? method, which is quite cool as it works like this:

anthem.include? "Aotearoa"

Which returns true, as anthem does indeed include "Aotearoa".



We're still on the first tutorial, but there's a shift in content so I'll break there.

Next we're looking at hashes, which seem roughly analogous to structs in CFML (so far, anyhow...), and we quickly get into symbols too. The tutorial got me to create a collection of books (the first was its suggestion, the others mine):

# books.rb
books = {}
books["Gravity's Rainbow"]    = :splendid
books["London Fields"]        = :splendid
books["The Da Vinci Code"]    = :abysmal
books["Once Were Warriors"]   = :quite_good
puts books
puts books.length

(I've not read Gravity's Rainbow, but I found The Crying of Lot 49 a bit self-indulgent, and have no reason to think GR is any different. The others are my own personal opinion).

This outputs:

{"Gravity's Rainbow"=>:splendid, "London Fields"=>:splendid, "The Da Vinci Code"=>:abysmal, "Once Were Warriors"=>:quite_good}
4


It's nice that Ruby works out that if I tell it to output a hash, it realises it needs to convert it to a string first (which it knows how to do), and does so. Rather than how in CF we'd just get an error. It shouldn't be too much to ask to expect CF to know to JSONify or (given its veneration) WDDXify a struct and then just output it, rather than being unhelpful:

Complex object types cannot be converted to simple values.


Thanks, CF.

Anyway, whilst hashes seem analogous to structs, there's no equivalent to symbols in CFML. I got what symbols were, but the docs for them weren't very descriptive (in a helpful way), so I googled about and came up with two pages which described them well:
So basically they're just a constant label which can be reused in situations like the above: where one wants to use the same value or token repeatedly. Things to note from those pages above are that symbols compare very quickly compared to strings: with two strings, one needs to compare each character; with a symbol, each side of the comparison either is or is not the same token. Also as tokens don't get garbage collected (ever), they can cause memory leaks if used injudiciously. I would say one would need to go mental with them to cause a problem though.

A quick digression:
  • Ruby is case-sensitive. This has been tripping me up when writing the tutorial code;
  • Quotes seems to be interchangeable. The tutorial is using single quotes - which I happen to detest for strings (chars: yes; strings: no. I blame C) - so I've been using double-quotes. They seem to work fine.
Oh bloody hell. The tutorial just got me to type this:

 books.valu­es.each {|rat­e| ratin­gs[rate] += 1}

Err... OK.

What that seems to do (having cheated and looked at the result) is for each of the values in the books hash, increment the key of that value's name in the ratings hash by one. But let me show the code I've typed in to show you what I mean.

# books2.rb
books = {}
books["Gravity's Rainbow"]    = :splendid
books["London Fields"]        = :splendid
books["The Da Vinci Code"]    = :abysmal
books["Once Were Warriors"]    = :quite_good
puts books
puts "\n"

ratings = Hash.new(0)
books.values.each {|rate| ratings[rate] += 1}
puts ratings

And the output is this:
{"Gravity's Rainbow"=>:splendid, "London Fields"=>:splendid, "The Da Vinci Code"=>:abysmal, "Once Were Warriors"=>:quite_good}

{:splendid=>2, :abysmal=>1, :quite_good=>1}


Note how it's tallied up the counts for each symbol in the books hash.

Right, so checking the docs, new can take an argument that acts as a default for any preciously unspecified hash entry, which is why when we increment ratings[rate] we don't get an error that it doesn't yet exist. That's neat!

And each does indeed call the following block for each element in the hash. each can pass either the value or the key and the value (if applicable) into the block, as the given name. In the given example we're just iterating over each value, which is a simple value itself, so we only give the anonymous block the value. However we could have been doing this:

books.each {
    |title,rating|
    puts "Title: " + title + "; rating: " + rating.to_s 
}

Wherein we're iterating over each book, and each book has a title (key in this case), and rating (value), so we pass both into the anonymous block. Note how I have to convert the rating to a string before I can concatenate it to the rest of the string, because rating is a symbol, not a string. The output for this is:

Title: Gravity's Rainbow; rating: splendid
Title: London Fields; rating: splendid
Title: The Da Vinci Code; rating: abysmal
Title: Once Were Warriors; rating: quite_good


So that line of code with all the braces and the pipes and stuff looked a bit scary initially, but once one stops to look at it an analyse it slightly, it's all straight forward.

Ooh... the next example of a block is a good 'un:

5.times { print "Odelay!" }

Obviously this prints out "Odelay!Odelay!Odelay!Odelay!Odelay!". But it's quite cool how the combination of  5 being an object, and  having iterative methods like times with anonymous blocks works, innit?

There's another shift in topic now, it looks like we're going to investigate file ops next...



The tutorial has demonstrated two different directory-reading methods:

# dir.rb
(Dir.entries ".").each{
    |fileName|
    puts fileName
}
puts "\n"

Dir["d*.rb"].each{
    |fileName|
    puts fileName
}

The output is this (it's of the directory I'm saving the sample files for this article in):

.
..
array.rb
arraySorting.rb
books.rb
books2.rb
books3.rb
books4.rb
dir.rb
gdnz.rb
gdnz2.rb
helloWorld.rb
helloWorldOO.rb

dir.rb


Here we use two different methods of the Dir class. entries returns an array of each file in the specified directory. And the [] method uses a pattern to match files in the current directory.

And do you know what? Ruby has just died a bit for me. The [] method is shorthand for the... the... the bloody glob method. And if you've been following along my forays into PHP, you know my opinion of the idea of calling a method "glob". Grumble.

By default when using Dir the directory is the current directory (ie: that the file was executed from). For example if I was to consider just this code:

# dir2.rb
Dir["*"].each{
    |fileName|
    puts fileName
}

I'm running this as follows:

C:\src\ruby\misc>ruby dir2.rb

So I get a listing of what's in C:\src\ruby\misc\

If I was to CD up a level and re-run it (I have the ruby/bin dir in my PATH, so can run it from anywhere), I'd get a listing of C:\src\ruby\. The default dir is predicated on where ruby was run from.

I can change the current dir as follows:

# dir3.rb
Dir.chdir "C:/apps/Ruby200-x64"
Dir["*"].each{
    |fileName|
    puts fileName
}

And this lists the contents of my Ruby install dir instead.

One other thing to note here is if we go back to look at dir.rb again:

(Dir.entries ".").each{

Notice I've got the parentheses around the first bit. Initially I tried to simply do this:

Dir.entries ".".each{

But that didn't work because it was trying to do each on ".", which of course is a) not likely to work as "." is a string and it doesn't have an each method; b) not what I meant anyhow. So the parentheses are just to force the correct order of operations. Sorting this out was another example of Ruby being fairly intuitive. I didn't have to look up what went wrong, I just kinda knew the parentheses would sort things out.

Carrying on with the tutorial it takes me through some file ops, and touches on date functions. There was nothing not completely obvious here, so I'll not go into detail (it copies a file, appends to a file, gets the file's timestamp: boring). But the relevant classes are File and Time. One good thing about the docs is that the URLs are pretty hackable. I wanted to get the docs for the File class, and was currently sitting on the docs for the Dir class (http://ruby-doc.org/core-2.0/Dir.html), and as I've been traversing through the docs, I know to simply change the class name, eg: http://ruby-doc.org/core-2.0/File.html and http://ruby-doc.org/core-2.0/Time.html. This seems trivial, but try doing that with the CFML docs. Or just trying to find the Railo docs ;-)

The tutorial is also telling me it covered using do / end instead of using braces around blocks. I didn't notice this, but I'll take their word for it. Here's a summary anyhow:

# array.rb
numbers = ["tahi","rua","toru","wha"]
numbers.each {
    |value|
    puts value
}
puts "\n"
numbers.each do
    |value|
    puts value
end

These output the same thing. So what's the difference. Well from my googling about it seems that braces have a higher precedence in the order of operations, so will always be bound to what's to their immediate left. However do/end have lower precedence, so might behave different. The second comment on this blog entry explains it well. I'll replicate it here:

f g { }

is parsed as f(g { }), where the braces belong to the method g. On the other hand,

f g do end

is parsed as f(g) do end, where the braces belong to the method f.

That's fairly clear, I think. Of course one can use parentheses to avoid this ambiguity in the first place. But I think using braces is clearer anyhow.



Blimey this tutorial is seeming very long (I've been writing this article for 5.5hrs now!) I think if I was not typing this as I went, the tutorial would be about 30min worth of work. Maybe an hour's worth. Oh well... it's certainly going into my head better this way.

Anyway, now we're looking at defining functions. Just looking at the tutorial example, the general form seems to be:

def function_name([arg [,arg[...]])
    # expressions
    lastExpression
end

eg:

def greet(name)
    "G'day " + name
end

puts greet "Zachary"

Note that there's no return statement, simply the value of the last expression is what the calling code receives from the function. Oh: every statement in Ruby is treated like an expression, so returns a value... I shoulda mentioned that earlier, sorry!

The tutorial also contrives a situation to use the require method to basically include some code one of the examples uses.

Next we're looking at classes, and - as we've seen above, once creates an object instance with the new method (it's a method of the Class class), just like we would in CFML.

Creating a class is bloody easy. Check out this basic example:

# PersonClass.rb
class Person
    attr_accessor :firstName, :lastName, :dob
end

kid = Person.new

kid.firstName = "Zachary"
kid.lastName = "Cameron Lynch"
kid.dob = Time.new(2011, 3, 24)

puts kid.dob

This outputs Z's birth date:  2011-03-24 00:00:00 +0000

There's no real surprises here... the attr_accessor method defines the properties of the class. Once defined, one can use implicit getter / setter notation on the object instance

I've been doing all this stuff in a file and then running it, but remember I've got the CLI open as well? I can just copy and paste that code into the CLI, and it all just runs fine, line by line

irb(main):100:0> class Person
irb(main):101:1>     attr_accessor :firstName, :lastName, :dob
irb(main):102:1> end
=> nil
irb(main):103:0>
irb(main):104:0* kid = Person.new
=> #<Person:0x00000002e2e558>
irb(main):105:0>
irb(main):106:0* kid.firstName = "Zachary"
=> "Zachary"
irb(main):107:0> kid.lastName = "Cameron Lynch"
=> "Cameron Lynch"
irb(main):108:0> kid.dob = Time.new(2011, 3, 24)
=> 2011-03-24 00:00:00 +0000
irb(main):109:0>
irb(main):110:0* puts kid.dob
2011-03-24 00:00:00 +0000
=> nil
irb(main):111:0> kid
=> #<Person:0x00000002e2e558 @firstName="Zachary", @lastName="Cameron Lynch", @dob=2011-03-24 00:00:00 +0000>
irb(main):112:0>

That is really cool. How useful it is? Hmmm... I've been using Windows too long for me to really get a buzz out of command-line hacking like some people do, but it's good to know it's there if one needs it. And I'm sure people will pipe up with a good use case. I suppose I still do a bunch of stuff with MySQL via the command line, so it's the same sort of thing. I'm just more familiar with MySQL than I am with Ruby (albeit I'm so rusty with MySQL, there's probably not much in it any more!).

The next step in the tutorial is adding a constructor method to the class, eg:

# PersonClass2.rb
class Person
    attr_accessor :firstName, :lastName, :dob
    
    def initialize (firstName, lastName, dob)
        @firstName, @lastname = firstName, lastName
        @dob = dob
    end
    
end

kid = Person.new "Zachary", "Cameron Lynch", Time.new(2011, 3, 24)

puts kid.dob

initialize is the equivalent of init() in CFML. Also note the @varName syntax for instance variables. And the slightly weird way one can assign multiple values to multiple variables at the same time.



And that's it. The last few pages of the tutorial went back over some array methods, but they weren't anything noteworthy.

I actually feel I know a bit about Ruby now. And I've only completed one of the seven tutorials on it they have. I'm absolutely fried after this... not the tutorial itself, but the seven hours I've been writing this blog article. Still: it filled up the afternoon. And you have something to read.

I'm gonna do the other tutorials too, and I'll write up my findings as I go.

Interestingly... I think I now know more about Ruby than I do about PHP. And which one of these am I supposed to be learning? Oops.

In closing: Ruby looks like quite a good language. I have only a superficial knowledge of it, but I found myself going "hey cool!" quite a bit as I worked through the tutorial and messing around with the sample code here. And having read each page of the tutorial or the docs, I found it very easy to write the code almost intuitively. I don't even get that feeling writing CFML even now, after a dozen years. I know better what to do in CFML, but until I've rote-learned something, it's not exactly intuitive. Which is odd for a language that claims it's dead easy to get up to speed with development in. It's interesting having a new perspective on this sort of thing.

If you're not sick of all this yet, I continue with my Ruby tutorials with the next lesson on Code School's Ruby track.

Right... I deserve a beer. I'm going up the road to have one.

--
Adam