Ruby basics
Ruby is "a dynamic, open source programming language with a focus on simplicity and productivity." (quoted from ruby-lang.org). Fortunately, you do not have to be a ruby expert to:
- experiment with Alf on www.try-alf.org,
- use the command line tool,
- write simple Alf-based scripts that connect to files and databases
Integrating the query language inside ruby-based software is another story, of course. This page provides you with sufficient knowledge of ruby for the scenarios above. Don't miss the Alf in Ruby and Alf in Shell pages for getting started concretely.
An important note first. Relational algebra can be seen as a purely functional
kind of programming. For this reason and on very intent, the overview here
does not cover variables, affectation (e.g. x = 2
) or operators that involve
mutation of shared state (e.g. classes and object-oriented programming). All
are valid in Ruby but not strictly necessary in the context of Alf.
Types and values
Data management is first and foremost about types and values. Alf uses the type system of ruby as a foundation. Useful value literals that you might encounter when learning Alf are:
'a string' # type: String
"a string" # type: String, similar but supports interpolation
12 # type: Integer
12.0 # type: Float
true # type: TrueClass, ... ruby has no Boolean, Alf fakes one sometimes
false # type: FalseClass
/[a-b]+/ # type: Regexp
:hello # type: Symbol (used by Alf for attribute names, aka AttrName)
[1, 2, 3] # type: Array (ruby supports heteroegenous arrays, we dont't use them here)
{a: 1, b: 2} # type: Hash (aka dictionnary, or map, Alf uses them a lot. see later)
->(t){ ... } # type: Proc (aka block or function, see later too)
Operators
Operators are seen here a pure functions that compute values from other values. Alf comes with a (somewhat strange) mix of infix, prefix and suffix syntaxes for operator invocations, all inherited from ruby. Unfortunately, you can't currently mix the styles, and have to follow the rules described now.
The infix notation is commonly used for comparisons, common arithmetic, boolean algebra, set-based operations on arrays, matching against regular expression, etc. A few examples you might encounter in Alf's documentation:
2 == 3 # false
2 < 3 # true
1 + 1 # 2
12 * 10 # 120
2 ** 8 # 258
true | false # true
true & false # false
[17, 13] | [13, 4] # [17, 13, 4], i.e. set-union
[17, 13] & [13, 4] # [13], i.e. set-intersection
"acbbcad" =~ /b+/ # 2, one-or-more 'b's found at index 2
The suffix notation is commonly used for invoking operators in an object-oriented kind of style (we also say 'sending a message'), and for selecting attributes on tuples (which is a special case of the former):
"hello".upcase # "UPCASE"
[1, 2, 3].empty? # false
[1, 2, 3].join(';') # "1;2;3"
t.name # "Jones" if t is Tuple(name: "Jones"), see later
The prefix notation is idiomatic in Alf when writing queries. This kind of syntax has been chosen for its functional nature, that nicely fits with the closure property of relational algebra (the fact that you invoke an operator on the result of a previous one):
project(join(shipments, parts), [:pid, :name, :qty])
We also occasionally use the prefix notation for invoking type selectors, also called "explicit coercion functions" in Alf:
Date("2013-10-17") # coerces a String to a Date value
Relation([ # coerces a Array of Hashes to a Relation
{name: 'Smith'},
{name: 'Jones'}
])
We also occasionaly define new operators. Ruby is dynamically typed and therefore does not require or even provide you with a way to make argument types explicit. For instance, for a scalar operator:
def double(x)
2*x
end
Another example, for defining a new relational operator as a shortcut for a longer expression (something we frequently use in this documentation and is also idiomatic for building domain-specific relational abstractions):
def in_london(operand)
restrict(operand, city: 'London')
end
Hashes
Hashes are very common in Ruby, so common that dedicated syntaxes exist
for them. A hash captures (key, value)
pairs, where both the key and the
value can be arbitrary values:
{ "city" => "London", "price" => 17.0, "suppliers" => ["Jones", "Smith"] }
Now, it is very common to use Symbols (e.g. :city
) instead of Strings for
keys:
{ :city => "London", :price => 17.0, :suppliers => ["Jones", "Smith"] }
A special syntax can be used in this case that is more compact. The following Hash denotes the same value as the one above:
{ city: "London", price: 17.0, suppliers: ["Jones", "Smith"] }
Hashes can of course be passed as arguments when invoking operators. A good
example is when invoking Alf's restrict
operator:
restrict(suppliers, {city: 'London', status: 20})
When a Hash is passed as last argument of an operator, the brackets can be safely ommited in ruby, leading to a nicer syntax:
restrict(suppliers, city: 'London', status: 20)
Procs
Procs can be seen as anonymous operators that can be passed as arguments and
used as any other kind of value. For instance, one may define the double
operator shown previously, using the anonymous syntax this time:
->(x){ x*2 }
A typical usage of Procs in Alf is the restrict
and extend
operators.
Observe how, in the second case, the Proc is actually mapped to a key in a
Hash:
restrict(suppliers, ->(t){ t.status > 20 })
extend(suppliers, big_name: ->(t){ t.name.upcase })