A guide to Ruby in ten lines of code
This is a slightly unorthodox introduction guide to the Ruby language. Through this guide we will examine just ten one-line snippets of code, and discover a lot of features from each of them.
This is the post version of a talk I gave for LibreIM.
Syntax
puts "Hello #{gets.strip}!"
This snippet accepts a string from standard input, then cleans the leading and trailing spacing characters and embeds it in a greeting which is shown on standard output. Notice that:
puts
andgets
are the standard functions for displaying messages and reading input, respectively.- You can omit parenthesis around parameters for better readability.
#{...}
in strings interprets small pieces of code and transforms the output into a string.
puts "boooored".upcase unless Time.now.saturday?
This example is so readable you probably don’t need a description. Notice that:
- Literals are Ruby objects and can receive methods.
if
,unless
,while
anduntil
can be used as one-line structures.- There is a convention to end methods which return booleans with
?
and methods which have non-explicit side effects with!
.
real, imag = (1 + 3i).rectangular
This snippet assigns the real part of 1 + 3i to real
and the imaginary part to imag
. Notice that:
- Ruby has built-in support for complex numbers, big numbers (as in symbolic, bigger than
long long
numbers) and symbolic rationals. - Assignment allows splatting arrays. The splat operator
*
allows for related operations. For example,x, *xs = [1, 2, 3, 4]
performs some Haskell-ish pattern matching:
x
receives the first element andxs
holds the rest of the array. There is also a similar double-splat**
for hashes.
Object oriented programming
class DeadPlayerError < StandardError; end
Defines a class useful to return errors (exceptions). Notice that:
- Classes are defined with
class
(which is syntax sugar forClass.new { ... }
). - There is simple inheritance with
<
. - You can throw an error with
raise DeadPlayerError
. Ruby distinguishes errors, from which a program can recover, and other kinds of exceptions, which leave the program in an invalid state and therefore it must stop.
Point = Struct.new :x, :y, :z
Creates a class with getters and setters for the given attributes. Notice that:
- Member attributes of a class are always private (or protected, as they are inherited). Thus, the dot only sends methods and doesn’t directly access attributes.
- Methods can be defined in a
do ... end
block. You should consider defining a class if you want to add them. - New objects are created with
Point.new
.
Iteration and data structures
puts %w[such elegant wow].each_with_index.map { |w,i| "#{i}. #{w}" }
Walks through the array, obtains an array of strings index. element and prints it on stdout. Notice that:
%w[]
splits a list of words by spaces and returns an array.each_with_index
iterates giving each element and its index in the collection.map
applies a function on each element of a collection and returns the result.{...}
odo ... end
denote blocks, structures of code which are passed to methods in order to be ran from them. Blocks can receive parameters between bars|a, b|
.
dot = ->(v1, v2) { v1.zip(v2).reduce(0) { |p, (n, m)| p + n * m } }
Computes the dot product. Example usage: dot.([1, 2, 3], [-1, 0, 2])
. Notice that:
- Lambda functions are defined with
->
. As opposed to usual functions or methods, Lambdas are objects. zip
pairs the elements of two or more arrays.reduce
accumulates results of a binary function.
fib = Hash.new { |h, i| h[i] = h[i - 2] + h[i - 1] }.update(0 => 0, 1 => 1)
Creates a Hash which contains, for each index, the corresponding term of the Fibonacci sequence. Notice that:
Hash
may receive a default initialization (usuallynil
). This initialization takes place whenever the user attempts to access a value which has not been assigned previously.Hash#update
assigns several values at a time.- This would be equivalent to a memoized recursive version.
solution.neighborhood.detect { |attempt, fitness| fitness > @current_fitness }
Assuming the .neighborhood
returns a collection of possible solutions and their performance (fitness), this finds the first one which improves the current solution. Actual use: https://git.io/vPxQ6. Notice that:
detect
receives a predicate and returns the first ellement of the collection which verifies it.- If you want to get the best solution in the neighborhood instead, you can use
max_by
. - There is a huge list of iteration methods available in any Ruby collection class:
Enumerable
I/O
open(DATA.read, "w").write IO.read($0).gsub(/^#' /, "")
This snippet reads itself (as in, the own program running), uncomments lines marked with #'
and passes the result as input to the program started by Kernel#open
. Notice that:
$0
is the name of the current program/script.gsub
performs global sustitutionKernel#open
opens read/write pipes with other processes when the “filename” looks like"|program_name"
.
The catch The program is hidden in the data section of the original script:
__END__
|pandoc -t beamer -o slides.pdf --pdf-engine=xelatex