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:
- putsand- getsare 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,- whileand- untilcan 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 longnumbers) 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: xreceives the first element andxsholds 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 ... endblock. 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_indexiterates giving each element and its index in the collection.
- mapapplies a function on each element of a collection and returns the result.
- {...}o- do ... enddenote 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.
- zippairs the elements of two or more arrays.
- reduceaccumulates 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:
- Hashmay receive a default initialization (usually- nil). This initialization takes place whenever the user attempts to access a value which has not been assigned previously.
- Hash#updateassigns 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:
- detectreceives 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:
- $0is the name of the current program/script.
- gsubperforms global sustitution
- Kernel#openopens 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