Sunday 27 July 2014

7Li7W: Ruby Day 1

G'day:
That's short of "Seven Languages in Seven Weeks", btw: some ppl get arsey when I write about non-CFML stuff on a blog claiming to be about CFML, so it's an enabler for them to know to piss off and do something else instead of read this.



As I noted earlier "Seven Languages [etc], and how guilt tripping me tends to work". Sean and Andy have colluded to guilt-trip me into cracking on and working through said book. Andy's now made it "worse" by declaring he's gonna work through it again at the same time. And has gone and shared his code too:


Peer pressure, I dunno.

Anyway, as I said in the earlier article, Day 1 didn't cover anything I didn't already a) know; b) covered elsewhere on this blog, but for the sake of completeness, here's a recap of the code.



# 1.rb

properties = [ 'object oriented' , 'duck typed' , 'productive' , 'fun' ] 
properties.each {|property| puts "Ruby is #{property}." } 

This - predictably - yields:

Ruby is object oriented.
Ruby is duck typed.
Ruby is productive.
Ruby is fun.


Notes:
  • So the array syntax in Ruby is the same as in CFML.
  • It also has an each array method like CFML has.
  • One does not need parentheses around method arguments (although sometimes it becomes necessary to disambiguate code)
  • However the code passed to each here is not a function, it's a "block". A lot of Ruby constructs take - in addition to any other arguments they might have - a "block". A block seems roughly analogous to an anonymous inline function. Ruby does make a distinction between blocks, procs and lambdas though. This is outside of the scope of this article.
  • There are two block syntaxes. The one here is delimited by curly braces, but this could also be delimited by do/end instead:

    properties.each do |property|
         puts "Ruby is #{property}."
    end
    

  • values passed into the block are done so between the pipe symbols. each passes each array element as an argument here.
  • one does not need to use semi-colons to separate statements, but one can if one so chooses (or to remove ambiguity).
  • Ruby does string interpolation via the #{} syntax.
Those are quite a few valuable points all in the space of a coupla lines of code.



Ruby's got a CLI, which one runs via IRB:


(the code is from the book, except for the "G'day", obviously)

This is bloody handy for testing things out. On a daily basis I use cflive.net which at least lets me use a throwaway CFML parser on a file-by-file basis, but IRB (and other's languages' equivalents) are even better as they're "just there" when one needs to test things. Like I have to an awful lot, with Ruby. There's an online one - if you don't have Ruby installed - @ repl.it.

That said, when I'm working through these exercises I save them to file & run them via Ruby itself:


I don't need a web server or anything, I just run the code.



Ruby is a pure object oriented language, which is quite cool. Not the OO part, per-se, but its purity. Even literals are objects:

# 4.rb

puts 4
puts 4.class
puts 4 + 4
puts 4.methods

This outputs:

4
Fixnum
8


So we can output a number (no news there); we can call a method on it (class is a method), and we can perform operations on it (also no news). It also outputs all this lot, as the result of the call to the methods method:


That's a whole swag of methods. Which are all in the docs for Fixnum, so you can look 'em up there.


Next we're looking at decisions. The meat and veg of this is what's true and what's false, which is demonstrated here:

# truthy.rb
cond = 1
puts "#{cond} appears to be true." if cond

cond = "random string"
puts "#{cond} appears to be true." if cond

cond = 0
puts "#{cond} appears to be true." if cond

cond = true
puts "#{cond} appears to be true." if cond

cond = false
puts "#{cond} appears to be true." if cond
puts "#{cond} appears to be false." unless cond

cond = nil
puts "#{cond} appears to be true." if cond
puts "#{cond} appears to be false." unless cond

The output for which is:

1 appears to be true.
random string appears to be true.
0 appears to be true.
true appears to be true.
false appears to be false.
 appears to be false.


The significant difference from CFML to note here is that only false and nil are false. Everything else seems to be true (including a very non-boolean string).

Also note that one can place a condition at the end of a statement, more clearly demonstrated here:

#ifUnless.rb
x = 4

puts 'This appears to be false.' unless x == 4
puts 'This appears to be true.' if x == 4

if x == 4
    puts 'This appears to be true.'
end

unless x == 4
    puts 'This appears to be false.'
else
    puts 'This appears to be true.'
end

puts 'This appears to be true.' if not true
puts 'This appears to be true.' if !true

And ruby has an unless statement as well as an if one. unless is simply the opposite of if: it performs the action when the condition is false. It does make the code more natural to read when one is more interested in a false condition.

Being able to put a condition at the end of a statement which is only run if the condition is met makes for simpler, more human-readable code IMO. There's not much in it, sure, but I can see a time and place for both the suffix approach, and the code block approach.

One can also use this suffix approach with while/until loops too:

#whileUntil.rb
x=0
x = x + 1 while x < 10
puts x
puts

x = x - 1 until x == 1
puts x
puts

while x < 10
    x = x + 1
    puts x
end

Output:

10

1

2
3
4
5
6
7
8
9
10



The last bit of the guidance for Day 1 demonstrates Ruby being duck-typed, but I think the author fails here. Here's the example they use:

i = 0
a = ['0100', 100.0]
while i < 2
    puts "Original: #{a[i]}"
    puts "Int: #{a[i].to_i}"
    i = i + 1
end

(I've tweaked the output slightly to make it more clear):

Original: 0100
Int: 100
Original: 100.0
Int: 100


This doesn't demonstrate duck-typing, it simply demonstrates one can explicitly cast from one type to another in ruby (in this case to_i, which casts to integer).

This would demonstrate duck-typing:

begin
    puts '100' + 100.0
rescue
    puts "puts '100' + 100.0 won't work" # so don't agree that was an example of duck-typing
end

But - as indicated - this errors because Ruby doesn't duck-type. The whole thing with duck-typing is that it happens automatically (like in CFML); so if one needs to explicitly perform the conversion, it's not duck-typing.


Each day of this book finishes with some homework. In today's case it was as follows (the comments are the homework instructions):

#homework.rb
load "sep.rb"

# A method that substitutes part of a string
# Information about Ruby’s regular expressions
puts ("this is a test string".gsub(/\b\w+\b/) { |match|
    match.reverse
}) 
sep


# Print the string “Hello, world.”
message = "hello world"
puts message
sep

# For the string “Hello, Ruby,” find the index of the word “Ruby.”
s = "Hello, Ruby"
puts s.index "Ruby"
sep


# Print your name [five] times.
name = "Zachary"
(1..5).each {|i| puts "#{name}"}
sep


# Information about Ruby’s ranges
# Print the string “This is sentence number 1,” where the number 1 changes from 1 to [5].
(1..5).each {|i| puts "This is sentence number #{i}"}
sep

# Run a Ruby program from a file.

NB: sep.rb just as this function in it:

# sep.rb
def sep
    puts "======="
end

I just use it to make the output more clear when I run code. The code above ran as follows:

siht si a tset gnirts
=======
hello world
=======
7
=======
Zachary
Zachary
Zachary
Zachary
Zachary
=======
This is sentence number 1
This is sentence number 2
This is sentence number 3
This is sentence number 4
This is sentence number 5
=======


The only thing of interest here is the gsub method which is the equivalent of CFML's reReplace(), except it takes a block which is passed each match, upon which one can run any code one wants, not simply a regex-replacement. That's cool. I've actually written a UDF that achieves the same thing for CFML, detailed in "replaceWithCallback() UDF for CFLib and suggested ER for ColdFusion".

The second part of the homework assignment was to write a quick guessing game. My implementation was thus:

#homework2.rb

# Bonus problem: If you’re feeling the need for a little more, write
# a program that picks a random number. Let a player guess the
# number, telling the player whether the guess is too low or too high.
# (Hint: rand(10) will generate a random number from 0 to 9, and
# gets will read a string from the keyboard that you can translate to
# an integer.)

number = rand(10)
guess = 0 # so it's in scope
loop do
    puts "Have a guess"
    guess = gets.to_i
    break if guess == number
    puts "too low" if guess < number
    puts "too high" if guess > number
end
puts "you got it: #{guess} == #{number}"

That's not how I'd write that code in the real world, but I was making a point of sticking to the stuff covered during that day's guidance.



That was it for Day 1. It took me much longer to write this up than it did to work through it. I learned a small amount, but also reminded myself a bunch of stuff I already knew.

At the time I'm typing this I've also completed Day 2, and read ahead Day 3 or Ruby and Day 1 of IO. I'll document all that stuff too, when I get time. At the moment I am not in a blog mood, so I'm just gonna drink beer instead (I'm at the airport, killing time).

Righto.

--
Adam