2015-02-02
This is all the Pragmatic Programmers fault. If they hadn't had a 50% discount on all ebooks, I wouldn't have started on this mad journey. Now, I've bought Seven languages in seven weeks and Seven more languages in seven weeks, and decided read the books, do all exercises in them plus some katas for each language and write a blog post about each language. All in the first six months of 2015. How am I going to find time to do that? I'm also supposed to work on the math app and the site. Well, well. We'll see if it works out or not, but at least I'm going to give it a try.
First a little note about the book Seven languages in seven weeks. Each language is presented in a separate chapter. Each chapter (at least so far...) is divided into an introduction to the language, three 'days' in which the author presents the important stuff, and a summary. In the end of each day-section there are some questions to be answered (mostly by googling, the answers are most often not to be found in the book) and some exercises to be done. The questions and exercises, which so far haven't been that difficult to complete, are meant to deepen the reader's understanding of the language.
It seems to me, after having read 1.5 chapters, that the author in the two first 'Day'-sections presents the most common building blocks of the languages, how to construct if-clauses, loops and so forth. The third day is devoted to something that he feels is a particular strength of the language. For Ruby for example, the topic of the third day section is metaprogramming.
I don't expect to become a LanguageX-expert by reading some 50 or so pages in a book about that language. My expectations after having read the chapter about a language are that I know the following:
- That the language exist (that's an easy one ;).
- What kind of language (functional, object oriented, strongly typed, weakly typed, compiled, interpreted, ...) it is.
- How to write simple programs using the most common language constructs.
- What the main strengths and weaknesses of the language are.
In the blog post about each language I will try to answer the questions above. I will also give my view about pros and cons of the language.
I hope that I will gain some new insights about language constructs that may help me become a better coder, and perhaps find a new language that I really enjoy programming in.
That was that about the book. Now over to the first language - Ruby!
Ruby is an object-oriented, dynamically and strongly typed, interpreted script language.
OK. So what does that mean? To me it means the following:
- Object-oriented: the data and behavour of the programs written in the language is encapsulated in objects. In Ruby everyting, even the primitive numbers is an object.
- Strongly typed: the types of the objects are checked and an error message is given if a type error is found.
- Dynamically typed: the type checking is executed at runtime, not at compile time (as is done statically typed languages)
- Interpreted: an interpreter is used to execute the programs. As opposed to a compiled language, in which a compiler is applied to the source code to transform it into a format that can be directly executed.
- Script language: a language in which it is possible to write scripts. I define a script as a program written to automate a task. Moreover, to me a script is a program that does not have to contain a main() method (or something similar). In its simplets form, it contains just a list of statements in a file and those statements are executed in order.
In addition to being strongly- and dynamically typed, Ruby is also duck typed. This means that (as you probably already know) it the actual fields and methods of object that determines what operations that are possible to invoke on the object, NOT its formal type. E.g. if we have two classes File
and Door
both of which contain an open()
operation. we can assign an instance of eiher of them to a variable and then use that variable it invoke the open()
operation regardless of it points to a File
or a Door
. As both classes contain an open
method, the invokation will succeed despite the fact that File
and Door
are different types.
As mentioned above, Ruby is a script language. It is thus possible to list some statements in a file (conventionally having the extension .rb) and then execute the statements in it using the Ruby interpreter ruby
. The traditional Hello World program can look like this in Ruby:
puts "Hello, world."
Listing 1: hello_world.rb. Hello World in Ruby.
The program can then be run by executing ruby hello_world.rb
in the shell. Alternatively the Ruby interactive interpreter, irb
, could be started and the program entered into it:
lars@lars-lilla:~$ irb
irb(main):001:0> puts "Hello, world."
Hello, world.
=> nil
irb(main):002:0>
Listing 2: Hello World in irb.
Variable usage in Ruby is very straight forward. It does not have any special keyword (e.g. def
) or type in its declaralation. Simple variable definition looks like this: i = 0
. Couldn't be any simpler, eh?
The convention is that variables start with a lowercase letter. The words in Multi-word variable names should be separated by underscores: important_value
. Apparantly, this is called snake_case.
No special characters should be used when accessing a variable, i.e. you don't have to prepend a $
to the name of the variable when accessing its value. I.e
a = 1
b = 2
c = a + b
puts "#{c}"
Listing 3: Accessing variable values.
In the example above, we also see the syntax for embedding the value of a variable in a string: #{variable}
.
Constants are defined by using SCREAMING_SNAKE_CASE
. E.g IMPORTANT_CONSTANT = 3
.
Note that the only difference to tell the difference between a variable and a constat is to look at their names. All caps => constant, not all caps => variable.
If one tries to reassign a constant, irb
complains but will perform the reassigment,
irb(main):001:0> A_CONST=42
=> 42
irb(main):002:0> A_CONST=3
(irb):2: warning: already initialized constant A_CONST
(irb):1: warning: previous definition of A_CONST was here
=> 3
irb(main):003:0> puts A_CONST
3
=> nil
Listing 4: Defining and reassigning a constant.
Arrays are defined and accessed using brackets:
an_array = []
an_array[0] = 'Hej'
an_array[1] = 42
puts an_array[0]
puts an_array[1]
puts an_array[2]
...
Hej
42
Listing 5. Array assignment and access
Note that no error message were given when an element that hasn't been assigned was accessed. Such elements evaluate to nil
which makes put
print an empty row.
The Array
class, that arrays are instances of, contain many methods. For example, an element may be appended to an array using the <<
method:
another_array = ['This is the item at index 0', 'This is the item at index 1.']
another_array << 'This is the item at index 2.'
puts another_array[0]
puts another_array[1]
puts another_array[2]
...
This is the item at index 0
This is the item at index 1.
This is the item at index 2.
Listing 6. Initializing an array and appending to it.
See its section in the official Ruby API documentation [1].
The syntax of an if-clause is the following:
a = 42
if a == 20
puts "Equal to 20"
elsif a < 10
puts "Less than 10"
else
puts "Not 20 and not less than 10."
end
Listing 7: Basic structure of an if-clause.
The logical operators come in two flavours: 'textual' and 'symbolic'. The textual variants are: and
, or
and not
. The variants using symbols are &&
, ||
and !
. The textual operators have lower precedence than the symbolic ones.
The comparison operators are: ==
, !=
, <
, >
, <=
and >=
.
if
:s can also be used as statement modifiers:
b = 3
puts "b is 2" if b == 2
puts "b is 3" if b == 3
...
b is 3
Listing 8: if as statement modifier.
Ruby supports a multitude of loops. The usual for
and while
loops of course, but also the little more uncommon until
.
A while loop looks like this:
i = 1
while i < 3
puts i
i += 1
end
Listing 9: While loop.
If one wishes, a do
can be inserted after the conditional:
i = 1
while i < 3 do
puts "Using do"
i += 1
end
Listing 10: While loop with do keyword included.
Note that do
do need to be locaded on the same line as the conditional. It is an error to place it on the next line.
while
can also be used as a modifier:
i=1
begin
puts "Inside while modifier loop; "
i += 1
end while i < 3
Listing 11: While used as modifier.
until
supports the same syntax, both as a regular loop:
i = 1
until i == 3
puts "Trying out until"
i += 1
end
Listing 12: until loop.
and as a modifier loop:
i = 1
begin
puts "Until as modifier"
i += 1
end until i == 3
Listing 13: until modifier loop.
for
loops can be used to loop over ranges
:
for i in 1..3
puts i
end
Listing 14: Simple for loop.
Note that the end index in the range is inclusive.
In additions to theese loop constructs, Ruby also supports more functional loop constructs like each
. The numeric classes also contain methods that can be used to create loops.
Ruby includes some statements to alter the flow of the loops. Theses are:
break
- breaks out of the (inner most) loop
next
- continues with the next iteration, if any, of the loop.
redo
- restarts same loop iteration again without trying the loop condition.
An example of controlling the flow of a loop using redo
:
i = 0
redone = false
while i < 3
puts "i = #{i}"
if i == 2 and redone == false
redone = true
redo
end
i += 1
end
...
i = 0
i = 1
i = 2
i = 2
Listing 15: Example of the redo statement.
A function is defined using the def
key word. Is is ended by the end
keyword. A simple function can look like this:
def say_hello()
puts "Hello"
end
Listing 16: Simple method.
A function may be called either with or without parentheses around the argument list:
say_hello()
say_hello
...
Hello
Hello
Listing 17: Parentheses are optional.
The order of the function definitions in a file is insignificant.
def b
puts 'B'
a()
end
def a
puts 'A'
end
b()
...
B
A
Listing 18: Order of function definitions is insignificant.
A function may however not be called before it is defined:
c()
def c
puts 'C'
end
...
function_call_order.rb:1:in `': undefined method `c' for main:Object (NoMethodError)
Listing 19: Function called before it is defined.
A simple class can be constructed like this:
class AClass
attr_accessor :anInstanceVariable, :anotherInstanceVariable
@@A_CLASS_VARIABLE = 42
# This is a constructor.
def initialize(aValue)
@anInstanceVariable = "This is an instance variable"
@anotherInstanceVariable = aValue
end
end
Listing 20: Example of a class.
Some things about classes:
- A class is defined by the
class
keyword.
- If the class inherits from another class the syntax is like this
ASubclass < ASuperclass
.
- Methods named
initialze
are constructors.
- Instance variables have names starting with a single @. E.g.
@thisIsAnInstanceVariable
- Class variables have names starting with two @-signs:
@@thisIsAClassVariable
A nice feature of Ruby's is that getters- and/or setters can be generated automatically. To use this feature, one lists the variables for which such methods should be used after one of the following keywords: attr_accessor
, attr_reader
or attr_writter
. The difference between them is that attr_reader
generates a getter, attr_writter
generates a setterer and attr_accessor
generates both a getter and a setter.
What about that colon sign before the variable names in the attr_accessor
list? That colon signifies a symbol. More about symbols in the section below.
An example program using the class we defined above can look like this:
require_relative 'a_class'
a_class = AClass.new("This is the value")
puts a_class.anInstanceVariable()
puts a_class.anotherInstanceVariable()
a_class.anotherInstanceVariable = "Changed value."
puts a_class.anotherInstanceVariable()
...
This is an instance variable
This is the value
Changed value.
Listing 21: Program using the example class.
The require_relative
statements makes it possible to access the class. The relative part makes the search for the class relative to the directory of the file the require_relative
is located in.
This section contain stuff that are...how shall I put it..somewhat special for Ruby as a language. They are not unique, I don't think very many programming language features exist that are included in only one language, but they are not mainstream either.
A symbol consists of a colon followed by an unquoted string (most often) or a quoted string (if the string contains white space).
What then is a symbol? Well, that is a rather difficult question to answer. I've googled a fair bit, and the best answer I've come up with is this: a symbol is an immutable thing that has both a string representation and an integer representation. The string representation (retrieved by calling to_s
) is equal to the name of the symbol (minus the colon). The integer representation is fetched using the to_i
method.
A sole instance of a symbol is referenced whenever that symbol is used in a program. This is the main benefit of symbols, I think.
See [2] for a nice and informative discussion about symbols.
Blocks are lambda expressions that can be passed to a function by adding a piece of code enclosed in either curly braces or do
-end
after the regular method parameters when invoking the function. Only one block can be passed to a method. The convention is that curly braces are used for one-liner blocks, and do
-end
for all other blocks.
A block can take zero or more parameters. Those are placed in a comma delimited list between |:s right after the left curly brace or do
.
An example:
an_array = [2,3,5,7,11]
an_array.each {|num| puts num}
...
2
3
5
7
11
Listing 22: Block taking parameters.
To call a block that has been passed into a function, the yield
keyword is used:
def method_receiving_a_block(regularParam)
puts "The regular (non-block param is #{regularParam})"
puts "About to call the block..."
yield
puts "...after the block"
end
method_receiving_a_block("this is the regular param ") {puts "Inside the block!"}
...
The regular (non-block param is this is the regular param )
About to call the block...
Inside the block!
...after the block
Listing 23: Calling block from the function receiving it.
In the function method_receiving_a_block
above, we see that the the block we passed into the function is called when the yield
statement is invoked.
If one wishes to pass parameters when invoking a block, those params can be specified when calling yield
:
def method_receiving_a_block(regularParam)
puts "The regular (non-block param is #{regularParam})"
puts "About to call the block..."
yield "Param to the block"
puts "...after the block"
end
method_receiving_a_block("this is the regular param") do |param|
puts "Inside the block!"
puts "The parameter to the block is: #{param}"
end
...
The regular (non-block param is this is the regular param)
About to call the block...
Inside the block!
The parameter to the block is: Param to the block
...after the block
Listing 24: Calling block with parameters from the function receiving the block.
A module in Ruby is a way to group classes, functions and constants so that they can be used as a unit. It provides a namespace that can be used to avoid name clashes. A module is imorted using the require
or require_relative
statements. The difference between require
and require_relative
is that require_relative
start searching from the current directory while require
searches the LOAD_PATH
.
An example of a simple module (defined in file a_module.rb)
module AModule
A_CONSTANT = 42
def AModule.a_method()
puts "Inside a_method"
end
end
Listing 25: An example of a module.
As can be seen in the example above, a function that is defined in a module should have the module name and a dot prepended to its name.
The module above can be used like this:
require_relative 'a_module'
puts "a_module.A_CONSTANT has value: #{AModule::A_CONSTANT}"
puts "Invoking the function a_method... "
AModule.a_method
puts "...back in the main program."
...
a_module.A_CONSTANT has value: 42
Invoking the function a_method...
Inside a_method
...back in the main program.
Listing 26: Example of using a module.
When using a constant defined in a module, the module name and a double colon should be prepented to the function name. Likewise, when invoking a funktion in a module, the name of the module must be specified.
A module can be included in a class. The module is mixed into the class. Let's see how that works.
A class in Ruby can inherit from only one superclass, i.e. Ruby supports only single inheritance. Something that resembles multiple inheritance can be emulated by mixing in one or more modules in a class. The constants and functions from those modules can then be invoked using an instance of the class. Consider this module:
module CModule
def c_method()
puts "Inside c_method"
end
end
Listing 27: Module meant to be mixed into a class.
It can be mixed into a class (using the include
statement) like this:
class BClass
require_relative 'c_module'
include CModule
def bMethod
puts "Inside the bMethod"
end
end
Listing 28: Class with a module mixed in.
We can now call functions from the CModule
using a reference to the BClass
:
require_relative 'b_class'
b = BClass.new
b.bMethod
b.c_method
...
Inside the bMethod
Inside c_method
Listing 29: Using the mixed in function.
Note that when the module is used as a mixin, the module name should not be prepended to the name of the functions in the module.
A class can more than one module mixed in.
So what's the difference between mixing in a module and using multiple inheritance? Here's my view: The intention of mixins is that a module that is mixed in should contain some well-defined piece of functionality that other classes might want to expose. Instead of a class A
inheriting from a class B
just to gain access to the functionality it exposes, and thereby saying that the inheriting class is a B
, mixing in a module M
exposes the same funktionality as B
without adding the is-a
relationship. A module can be mixed into two (or more) totally unrelated classes that only have in common that they want to expose the functionality of the module. In summary: exposing a shared piece of functionality without adding the is-a
-relationship.
Metaprogramming is the fine art of writing programs that manipulates itself and/or other programs. In Ruby, one way of implementing metaprogramming is by overriding the method_missig
method in a class. When doing that, all method calls to non-existing methods in the class will be routed to method_missing
.
An example:
class CatchAllClass
def method_missing(name, *args)
if name.to_s == "secretMethod"
puts "You found the hidden method!"
else
puts "That does not compute. Method: '#{name}' does not exist"
end
end
end
Listing 30: Example of implementation of method_missing() in a class.
The class above can be used like this:
require_relative 'catch_all_class'
c = CatchAllClass.new
c.doesThisWork?
c.secretMethod
...
That does not compute. Method: 'doesThisWork?' does not exist
You found the hidden method!
Listing 31: Using the class that implements method_missing() .
This is, of course, a toy example. In s more serious program, we could use method_missing
to for example handle queries to a database where the name of the called method determines which parameters to use in the selection criteria of the query. This is e.g. what the persistency mechanism in Ruby on Rails does.
There are also other metaprogramming constructs in Ruby. Among other things, methods can be added to classes at runtime using define_method
.
Here are some things that tricked me while writing some example programs in Ruby:
When using logical operators in conjunction with the ternary operator, my assumption was that ?
had lower precedence than and
and or
, but such is not the case in Ruby. An example:
a = true
b = true
s = a and b ? "First" : "Second "
puts "#{s} branch chosen"
s = (a and b) ? "First" : "Second "
puts "#{s} branch chosen"
...
true branch chosen
First branch chosen
Listing 32: Ternary operator precedence.
As the example above shows, the logical operator and
have lower precedence that ?
. This is not the case in neither Java nor Python, which tricked me.
From Python, I'm used to the end index being excluded when looping over a range of values. Not so in Ruby. Here the end index is included.
for i in 1..5
puts i
end
...
1
2
3
4
5
Listing 33: Ending element of a range is included..
The FizzBuzz kata is the simplest kata that I've tried out. For those of you that haven't come across it, the task is to print the numbers from 1 to 100, but when a number is divisable by 3 and/or 5 a word should be printed instead of the number. When a number is divisable by 3, 'Fizz' should be printed and when a number is divisable by 3, 'Buzz' should be printed. If the number is divisable by both 3 and 5 the word 'FizzBuzz' should be displayed.
This my implementation of the FizzBuzz kata in Ruby:
1.upto(100) do |x|
print "Fizz" if x % 3 == 0
print "Buzz" if x % 5 == 0
print x if x % 3 != 0 and x % 5 != 0
puts
end
Listing 34: FizzBuzz in Ruby.
Ruby is a nice language which I like to program in. It has a nice feeling. More specifically, these are some of the things I like with Ruby:
- The language is compact, but not too compact. Since I last visted Ruby, it seems that the
do
starting the body of a function has been made optional. This is nice I think. Of course, this is a very minor detail, but still. Now, if they can get rid of the end
as well...
- blocks - nice implementation of lambdas/closures.
- inline form of
if
- puts "Hello!" if name == "Lars"
attr_accessor
and its siblings. Nice way to avoid writing the getters and setters that often make up a not unsignificant amount of the code in a Java program.
Here are some of the things I don't like about Ruby:
- That the parentheses are optional when defining a function. I like it when it is very clear what kind of language construct the piece of code I'm looking at belongs to. I don't like when I'm forced to stop and think about if it is a function definition, a function call, a lambda expression or something else. My opinion is that skipping the parentheses around the parameter list when defining a function is making the code less clear, and therefore should be avoided.
- That the parentheses are optional when calling a function. For the same reason as stated in the bullet above.
- That the
return
statement of a function is optional even if the function is designed to return a value. If ommitted, the value of the last expression is returned. Not including an explicit return statement is also making the code less clean. An exception might be a single line getter method. But the number of characters saved when writing foo
instead of return foo
isn't very many, so it might be better just to always use return
when returning a value from a function.
That was that regarding Ruby. I really like the language and hope that I will get to program in it professionally some time. I think that I've learned quite a bit about the basics of the language. To be honest I think that I learnt more from reasearching stuff for writing this blog post than from reading the Seven languages in seven weeks book. But it also took alot longer to write the blog post than to read the Ruby chapter in the book and doing the exercises in it, so that might be as it should.
Now it's time to turn to the next language in line: Io. Somehow I think it will be...very different. I sure that it will be fun to learn something about it. I'd better get started...
[1] Ruby API documentation - Arrays
[2] The Ruby Newbie Guide to Symbols
[3] Ruby API documentation
2015-03-29
Io is a prototype based language. What does that mean then? It means that the only way to create an object is to create a clone of another object. The object that is cloned is called the prototype. There aren't any classes in Io. Only objects.
A central concept in Io is a message. A message can be sent to an object (which when receiving a message is called a receiver), which then responds to the message by returning a receiver (itself or another object), which then can be the target of another message and so on. The whole Io language is based on chaining receivers together by means of messages. The object sending the message to the target
is called the sender
.
Another central concept is that of a slot. A slot is similar to an entry in a hash table. It has a name (comparable to the key of the hash table entry) and, possibly, a value. An object can contain an unspecified number of slots and new slots can be added on the fly, so in this sense an object is like a hash table. In fact, there isn't much more to an object than it being a collection of slots. Sounds a little boring? Maybe, but one can accomplish quite a lot with just a set of slots.
So, what can be put in a slot? The most obvious thing to put in a slot is of course some piece of data, e.g. a string. The value of the slot can then be accessed by sending a message containing the name of the slot to the object. But a data value is not all that can be put in a slot. It turns out that also methods can be stored in slots. It is by putting methods in the slots of an object and then sending a message contaning the name of the method to the object that methods are invoked.
An Io program is executed by running the interpreter io
that comes with the Io installation bundle. After having started the interpreter a prompt looking like this will be displayed:
Io 20110905
Io>
Now programs can be entered at the prompt.
Another way of executing an Io program is to give the name of a file containing an Io program as a parameter to the io executable: io myProgram.io
.
Comments can be created using //
, /**/
or #
syntax.
Hello World is as simple as to send the println
message to the "Hello, World!"
object:
"Hello, World!" println
...
Hello, World!
Listing 1: Hello World in Io.
To create an object to start working with, one can be cloned from the Object
prototype. From that object, other objects (both types and other objects) can then be created. To create a type, the name of the clone should start with a capital letter. Regular, non-type objects have names starting with a lowercase letter.
Animal := Object clone
Dog := Animal clone
fido := Dog clone
Listing 2: Cloning objects to create other objects.
Instead of having variables, Io has, as I mentioned above, slots. A slot can be created and assigned using the :=
operator. If a slot already exists, a value can be assigned to it using the =
operator. The ::=
operator is like :=
but in addition to creating a slot and assigning to it, it also creates a setter method for the slot.
When assigning values to slots, the following syntax is used objectOrType nameOfSlot := value
An example:
MyType := Object clone
MyType mySlot := "This is a value for a slot in a type"
MyType mySlot println
myObject := Object clone
myObject slotInObject := "This is a value for a slot in an object"
myObject slotInObject println
...
This is a value for a slot in a type
This is a value for a slot in an object
Listing 3: Assigning values to slots in types and objects..
Something that confused me at first when I was looking at example programs that I found on the net, was assignments to slots where the object or type of he slots wasn't specified. Like this: a_slot := 42
. When I experimented a bit, it turned out that when using that syntax, the type Object
is implicit. I.e. if no object or type is specified when assigning or accessing a slot, the slot is assumed to belong to Object
. This syntax is quite handy for program parts where a regular variable should be used in a conventional programming language. An example:
a := 1
b := 2
c := a + b
"In Object a has the value: " print;Object a println
"In Object b has the value: " print;Object b println
"In Object c has the value: " print;Object c println
...
In Object a has the value: 1
In Object b has the value: 2
In Object c has the value: 3
Listing 4: When no object or type is specified, it defaults to Object.
In listing 4 above, I've used semicolons to separate expression instead of having each expression on a separate line. This was done to group the expressions in a more logical way (and had the nice side effect of showing the usage of ; as an expression separator ;)
Methods are defined by creating a method
object and assigning it to a slot of an object. The name of the slot becomes the name of the method. To invoke the method on the object send the name of the method/slot to the object. An example:
Vehicle := Object clone
Vehicle description := method("Driving...." println)
Vehicle description
...
Driving....
Listing 5: Creating and invoking a no-arg method..
To define a method that takes parameter, specify the names of the parameters as the first arguments in call to method
:
Multiplier := Object clone
Multiplier timesTwo := method(n, n * 2)
Multiplier timesTwo(4) println
Multiplier multiply := method(n, m, n * m)
Multiplier multiply(7, 8) println
...
8
56
Listing 6: Defining methods taking arguments.
As can be seen from the example above, when calling a method, the arguments are specified in a comma separated list enclosed in parenthesis.
A list
is an ordered collection of objects. In Io all lists have List
as (one of) their prototypes. A list can be created by using the lists()
method on Object
:
a_list := list("a", "b", "c", "d")
a_list println
...
list(a, b, c, d)
Listing 7: Creating a list.
The List
class supports a lot of different methods. For example, an element can be added using the append()
method:
a_list append("e")
a_list println
...
list(a, b, c, d, e)
Listing 8: Appending to a list.
A value in a list can be retrieved using the at()
method and it can be set using the atPut()
method.
b_list := list(1,2,3)
b_list at(1) println
b_list atPut(1, 42)
b_list at(1) println
...
2
42
Listing 9: Getting and setting values in a list.
Other methods List
:s support are for example: size()
, pop
, at()
, prepend()
, sum()
, average()
and isEmpty()
.
Io also supports a hashtable like datastructure: Map
. Basic usage of a Map
can be seen in the example below:
aMap := Map clone
aMap atPut("a", "The letter a")
"The element for key a" println
aMap at("a") println
"
Now adding a value for key b" println
aMap atPut("b", "The letter b")
"
Printing the map as a list..." println
aMap asList println
"
...and as an object:" println
aMap asObject println
"
The number of elements in the map is:" println
aMap size println
"
The keys of the map are:" println
aMap keys println
Listing 8: Basic usage of Map.
There are a couple of different ways in which an if clause can be constructed. The first way is to use the if()
construct taking two (no else clause) or three (else included) parameters. The first parameter is the boolean expression on which the if
clause should be based, the other parameter is the message to evaluate if the boolean expression is true
, and the third parameter represents the message to evaluate if it was false. An example:
if(7 < 42, "7 less than 42" println, "7 greater than 42" println)
...
7 less than 42
Listing 10: If clause, variant 1.
The second way to create an if clause is to the if() then() else()
construct. Here the boolean expression goes in the if()
method, the message to evaluate in the true branch in the then()
and the message to evaluate in the false branch in the else()
(which of course is optional). The previous example rewritten to use this construct looks like this:
if(7 < 42) then("7 less than 42" println) else("7 greater than 42" println)
...
7 less than 42
Listing 11: If clause, variant 2.
There also exists an elseif()
construct:
if(87 < 42) then("87 less than 42" println) elseif(87 == 42) then("87 equal to 42" println) else("87 greater than 42" println)
...
87 greater than 42
Listing 12: If clause containing both an elseif() and a else() clause.
The third variant of an if clause is the ifTrue()
and ifFalse
constructs:
(87 < 42) ifTrue("87 less than 42" println) ifFalse("87 greater or equal to 42" println)
...
87 greater or equal to 42
Listing 13: If clause, variant 3.
An important thing to notice is that the code to execute in one of the branches does not have to fit in a single line. An (almost I suppose...) unlimited number of messages can be constructed by separating them using semicolons or by putting them om separate lines. An example:
if(7 < 42,
"We all know that 7 is less than 42..." println
"...but I want to stress the fact anyway." println
)
...
We all know that 7 is less than 42...
...but I want to stress the fact anyway.
Listing 14: If clause with a multi-message code section in its true branch. Messages separated by a newline.
The program above could equally well have been written using the message separator ;
if(7 < 42,
"We all know that 7 is less than 42..." println ; "...but I want to stress the fact anyway." println
)
...
We all know that 7 is less than 42...
...but I want to stress the fact anyway.
Listing 15: If clause with a multi-message code section in its true branch. Messages separated by a semicolon.
When using the if clause with an else part and where the true/false sections contain multiple messages separated by ; one has to be extra cautious not to mix up the comma separating the true branch from the false branch and the semicolons separating the messages. In these situations, where both the true branch and the false branch contain multiple messages, it is probably better to separate the messages with newlines instead of semicolons. When I think about it, it is probably almost always better to separate the messages with newlines instead of semicolons ;)
Io supports the following looping constructs:
loop
: loops infinitely.
repeat
: loops a specified number of times.
for
: loops over an integer interval, possibly with a step.
while
: loops until a boolean expression evaluates to false.
The following examples illustrates the different kinds of looping constructs.
i := 0
loop(
i println
i = i + 1
if(i>=3, break)
)
...
0
1
2
Listing 16: Example of using loop.
In the example above, we also see the usage of break
, which, as usual, breaks out of the innermost loop. Io also supports continue
.
3 repeat("Hello!" println)
...
Hello!
Hello!
Hello!
Listing 17: Example of looping using repeat.
for(i, 1, 10,
i println
)
...
1
2
3
4
5
6
7
8
9
10
Listing 18: Example of the for construct not using an explicit step (=> step of 1 is used.).
An explicit step can also be specified for for
loops.
for(i, 1, 10, 2,
i println
)
...
1
3
5
7
9
Listing 19: Example of a for loop having an explicit step.
i := 0
while(i < 3,
i println
i = i + 1
)
...
0
1
2
Listing 20: Example of a while loop.
As was told before, message passing in Io is build on sender
:s sending messages to targets
that executes the message. The retrieve metadata about a message, the sender of the message or the receiver of the message the call
function can be used.
call
can be used in these forms (among others):
call sender
- retrieve the sender the message currently being processed.
call target
- retrieve the target the message currently being processed.
call message name
- retrieve the name of the message currently being processed
call message arguments
- retrieve the arguments of the message currently being processed.
An example program to demonstrate the usage of call
can be seen in the listing below:
# Sets up a chain of three objects: right - middle - left, where a message is sent from
# the right hand object, through the middle object to the left hand object.
# The purpose is to illustrate the usage of the call function to retrieve metadata about
# the sender, target and the message itself.
# Create the objects.
rightHandObject := Object clone
middleObject := Object clone
leftHandObject := Object clone
# Create a method that should receive the message and print it.
leftHandObject receive := method(arg,
"LHO received: " print
arg println)
# The middle object should forward the message after having used call to print metadata about it.
middleObject objectToSendTo := leftHandObject
middleObject passThrough := method(txt,
"*** In MiddleObject.passThrough" println
"*** Sender" println
call sender println
"*** Target" println
call target println
"*** messge name" println
call message name println
"*** messge args" println
call message arguments println
self objectToSendTo receive(txt))
# The right hand object should start the message chain.
rightHandObject objectToSendTo := middleObject
rightHandObject send := method(txt,
self objectToSendTo passThrough(txt))
# Initially, we print some info about the objects so that we can compare the hashcodes of the
# objects with the metadata printed by the middle object. This way we can figure out which
# objects that are returned by call sender and call target.
"***********" println
"** RHO: " println; rightHandObject println
"** MiddleObject: " println; middleObject println
"** LHO: " println; leftHandObject println
"***********" println
# Start the message passing by sending a message to the right hand object.
textMessage := "Hello"
"Sending " print; textMessage print; " to rightHandObject" println
rightHandObject send(textMessage)
Listing 21: Using call to retrieve metadata..
Output from a run of the program can look like this:
***********
** RHO:
Object_0x24e41d0:
objectToSendTo = Object_0x241ed40
send = method(txt, ...)
** MiddleObject:
Object_0x241ed40:
objectToSendTo = Object_0x241ed00
passThrough = method(txt, ...)
** LHO:
Object_0x241ed00:
receive = method(arg, ...)
***********
Sending Hello to rightHandObject
*** In MiddleObject.passThrough
*** Sender
Object_0x24e41d0:
objectToSendTo = Object_0x241ed40
send = method(txt, ...)
*** Target
Object_0x241ed40:
objectToSendTo = Object_0x241ed00
passThrough = method(txt, ...)
*** messge name
passThrough
*** messge args
list(txt)
LHO received: Hello
Listing 22: Output from the program illustrating the usage of call.
We see that, as expected call sender
returns the right hand side object and call target
return the middle object (which is executing the call target
). By using call message name
and call message arguments
we can retrieve the name of the called method (passThrough
) and its arguments.
Object reflection involves examining the slots and the prototypes of an object (or type). The slots of an object can be retrieved by sending the slotNames
message to the object. Which prototypes an object has can be found out using the proto
message.
The following example demonstrates how slotNames
and proto
can be used.
# Add a method that recursively prints the names of the slots of this type and its prototypes to Object. When the recursion reaches the level just before Object it is stopped.
Object printSlots := method(
# Print the slots of this object
"The slots of " print
self type print
" are: " print
self slotNames foreach(slotname,
slotname print
", " print
)
"" println
# Recursively call printSlots() for all prototypes.
# The prototype of Object is Object => we have to check
# if the prototype is Object to avoid infinite recursion.
if (self proto != Object, self proto printSlots())
)
# Create a new type having one slot containing data and one slot containing a method.
MyType := Object clone
MyType mySlot := "This is a value for a slot in a type"
MyType aMethod := method("This is a method" println)
# Create another type by cloning the first type.
AnotherType := MyType clone
# Print the slots of the second type (which recursively will print the slots of its prototypes (minus Object)).
AnotherType printSlots()
Listing 23: Usage of slotNames and proto.
The concurrency support in Io seems rather nice. Let's delve a little bit into that.
A coroutine is a function that voluntarily suspends, or yields, its own execution to let some other piece of code (which most likely is a coroutine as well) run. When the other code piece has finished, the function resumes its execution and runs until it terminates or yields again. This way, the execution can switch back and forth between a number of functions that will cooperate to produce the final result of the program.
A function in Io can yield the execution by using the yield
keyword.
Another piece of the Io language that one has to know about when playing around with coroutines, is that messages can be sent asynchronously to an object by prepending either @ or @@ to the name of the message when sending it. The difference between @@ and @ is that the former returns nil
and the latter a future (see the section on futures below). If a message is sent synchronously to a coroutine, there will be no other coroutine executing when yielding, so one has to use asynchronous message passing when playing around with coroutines. Let's try it out.
The following is a small program containing two objects each having a method that writes two strings to standard out. Between the two write statements, there is a yield
that lets another concurrently executing method have a go. In the first version of the program, we invoke the two methods synchronously:
man1 := Object clone
man1 talk := method(
"Man 1 says hello!" println
yield
"Man 1 says nice to meet you" println
)
man2 := Object clone
man2 talk := method(
"Man 2 says hello!" println
yield
"Man 2 says nice to meet you" println
)
man1 talk; man2 talk
...
Man 1 says hello!
Man 1 says nice to meet you
Man 2 says hello!
Man 2 says nice to meet you
Listing 24: Synchronous invocation of coroutines. Not much point in doing this.
As we see, the output is as expected: first the first function executes, then the other function executes.
If we change the invocation to be asynchronous however, the result is different:
man1 := Object clone
man1 talk := method(
"Man 1 says hello!" println
yield
"Man 1 says nice to meet you" println
)
man2 := Object clone
man2 talk := method(
"Man 2 says hello!" println
yield
"Man 2 says nice to meet you" println
)
man1 @@talk; man2 @@talk
Coroutine currentCoroutine pause
...
Man 2 says hello!
Man 1 says hello!
Man 2 says nice to meet you
Man 1 says nice to meet you
Listing 25: Asynchronous invocation of coroutines. Interleaved execution.
Now the execution of the two methods were interleaved. We also see that in this particular execution man2 gets to execute first, but this is just a coincidence. It could equally well have been the other way around.
We've used the Coroutine currentCoroutine pause
to make the program wait until all asynchronous methods have finished executing. Without it, the main program could have terminated first, before the two methods, and then there would have been no output, which is kind of boring.
An actor is a concept used in computer science to model a concurrent entity. When receiving a message, an actor can perform business logic (e.g. change its own state), create more actors and send messages to other actors. What it cannot do is to change the state of other actors. It communicates with other actors by sending messages to them. This is the advantage that using actors instead of threads for concurrent programming. Threads may share state, which may lead to all kinds of problems, eg. race conditions and deadlocks.
So how does one create an actor in Io? By sending a concurrent message to a regular object! Simple, isn't it? Let's exemplify.
We create two objects each having a method that waits for a certain amount of time before printing a message. When we call the messages synchronously, we see that as expected, the methods execute in order:
slower := Object clone
slower run := method(
wait(2)
writeln("In slower")
)
faster := Object clone
faster run := method(
wait(1)
writeln("In faster")
)
slower run(); faster run()
...
In slower
In faster
Listing 26: Invoking synchronously => regular object.
When we invoke the methods synchronously, however, the two objects become actors, and the faster object terminates its execution more quickly than the slower one:
slower := Object clone
slower run := method(
wait(2)
writeln("In slower")
)
faster := Object clone
faster run := method(
wait(1)
writeln("In faster")
)
slower @@run();faster @@run()
wait(3)
...
In faster
In slower
Listing 27: Invoking asynchronously => actor.
A future is an object returned from an asynchronous invocation of a method. The future is returned from the method directly when the method starts executing not, as a regular result object, in the end of the method when its execution is about to end. A future contain a synchronous method to retrieve that final answer, however. An invocation of that will not return until the original method has completed its work and returned the result. This means that a program can invoke a method, store the future returned by that method, perform some other task and, when that task is finished, retrieve the result computed by the method by invoking the synchronous method on the future. This way the program can perform useful work at the same time as the method executes instead of just waiting for the method to terminate. Smart, isn't it?
An example:
futureResult := URL with("http://www.larsnohle.se") @fetch
writeln("This will execute at once")
# Perform useful work here!
resultSize := futureResult size
writeln("Has got the result. The size of it was: ", resultSize)
...
This will execute at once
Has got the result. The size of it was: 1838
Listing 28: Using a future to retrieving the result of an asynchronous method call.
The message This will execute at once will be printed right away. Then some time will pass before the web page will be retrieved and the second message will be printed. In this program no useful work is performed after the call to @fetch, but in a real world program some calculation could have been performed here.
Io, of course, contains many other features as well. One of them is support for metaprogramming by redefining the forward
message of Object
.
forward
can be use to pick up messages to an object that the object normally wouldn't handle. As an example, we write a small program that redefines forward
so that the name of the non-existing message will be printed, and so will also any string arguments of that message.
Greeter := Object clone
Greeter forward := method(
write("The name of the message is: ")
call message name println
call message arguments foreach(arg,
content := self doMessage(arg)
if (content type == "Sequence",
write("Found a string argument: ")
content println)
)
)
greeter := Greeter clone
greeter say("Telefonbanken")
...
The name of the message is: say
Found a string argument: Telefonbanken
Listing 29: Redefining forward
Here are some things that tricked me while writing some example programs in Io:
- Repeatedly using
=
instead of :=
- Trying to separade statements to execute in an
if
clause using commas instead of newlines.
My, non-optimal, implementation of the FizzBuzz kata in Io looks like this:
for(i, 1, 100,
if(i % 3 == 0,
"Fizz" print
)
if(i % 5 == 0,
"Buzz" print
)
if(i % 3 != 0 and i % 5 != 0,
i print
)
"" println
)
Listing 30: FizzBuzz implementation in Io.
Io is an interesting language. The syntax is strange until you get the hang of it. I've never programmed in a language that is anything like it. This makes it rather fun to use Io. It feels a little bit like you are playing an adventure game trying to explore a world that has just been discovered. It's great fun!
I also like the smallness of the language. It seems that the core language is quite small. There are a very limited number of language constructs that one has to learn. Once you have learned those, I think that the language will not give you very many suprises regarding its syntax.
On the technical side, I like the concurrency support of Io. It it extremely easy to send an asynchronous message instead of a synchronous message to an object (just prepend @ or @@ to the message name). Prototype based inheritance is also interesting. I'm not sure that I like it really, but it is something...different, which makes it interesting. At least in the beginning.
It is quite hard to find information about Io. There are the official home page, which contains, how shall I put it, brief descriptions about most of the Io syntax. The problem is that the descriptions are so brief that if one wishes to do something just a little bit out of the ordinary, the chances are that that isn't covered by the documentation. Then the second problem kicks in: it is not easy to find any other resource regarding Io on the Net. Googling for an Io related topic doesn't will not give you very many hits. And the hits found will probably be either links to the official home page, or to some blog where a solution to one of the exercises in the Seven Languages in Seven Weeks book are discussed (which of course is helpful if you are struggling with with those...)
The time it takes to write these blog entries about the "14" languages takes more time than I had anticipated. Something always comes up that I have to do before I can continue reading/trying out the language/writing about it: work, life, another topic to write about. Therefore I think I have to revise my original plan of going through all 14 languages in the first six months of 2015. My aim now is going through the first seven languages in the whole of 2015. I slight modification of the plan... But hopefully it is more realistic. If I get around to doing more than seven, it's good, but I won't be disappointed if I don't. Well, well. We'll so how it goes.
Now on to the next language: Prolog! But first it seems that I have to study up a little bit on Docker...
1. Ruby
[1] Io Programming Guide
[2] Wikibooks: Io Programming
[3] Explaination of Actors.
2015-04-29
Prolog is a language unlike every other that I have programmed in. In Prolog one does not tell the computer "first you do this, then you do that". You define a number of facts and a number of rules connecting the facts. Then you perform queries to get answers to the questions you have. Prolog uses the facts and the rules you have defined to try to answer you questions.
A prolog program is run in an interpreter. I use GNU Prolog (gprolog) as that is the prolog interpreter used in the Seven Languages in Seven Weeks book.
The GNU Prolog interpreter is (surprise!) started by executing the gprolog
command.
A prolog program can either be entered directly at the gprolog
prompt or be loaded into the interpreter using the command ['filename']
which will load the file filename.pl
from the current directory.
A fact is a statement that something, e.g. lars, is (or has, or does or...) something, e.g. a programmer. To express the fact "Lars works as a programmer", the following Prolog code can be used occupation(lars, programmer).
Note the dot at the end of the statement. Every statement in Prolog is ended by a dot. If one forgets the dot, the Prolog interpreter does not display anything. Its cursor just continues to blink to indicate that more input is needed.
Another important thing to notice is the case of the first letter of "lars" and "programmer" in the example above. The lowercase letters of those words means that the values are constant. In Prolog speak, they are atoms. If the first letter should have been a capital letter they would instead be variables.
A rule is a combination of the stated rules to form a conclusion about something. E.g. a person like coca cola if that person is a programmer.
A rule is defined using the :-
operator. To the left of the operator is the name of the rule and the list of parameters it takes. Note that the first letter of each parameter should be a capital one. If not, they become atoms instead of variables, which makes it impossible to pass a value for that parameter (other than the constant value of the atom).
An example of a rule:
likes_coca_cola(X) :- occupation(X, programmer).
In the listing above, we create a rule that says that every person who works as a programmer likes coca cola.
Rules are also called inferences.
It is possible to define rules having the same name, but taking different number of arguments. E.g. both a calculate(X)
rule and a calculate(X, Y)
rule can exist. In prolog speak the first of those rules are called calculate/1
and the second calculate/2
. More generally, a rule is written as nameOfRule/numberOfParamsItTakes
.
The facts and rules form a kind of world. Queries can be used to ask questions about that world to get the answers one is interested in. E.g. Does Lars like Coca Cola?
To obtain answers to queries are the whole point of using Prolog. They are the end products of a Prolog program.
To create a query to determine if lars likes coca cola, we invoke the likes_coca_cola
rule and pass the atom lars
as parameter:
| ?- likes_coca_cola(lars).
yes
lars
gets bound to the variable X
in the rule, and we see that, as expected, lars
likes coca cola.
We can also ask Prolog to find all people that like coca cola. We do this by specifying a variable instead of an atom in the query:
| ?- likes_coca_cola(X).
X = lars
yes
We see that Prolog again has determined that lars likes coca cola. In this example, we've only defines one programmer so Prolog terminates the execution of the query directly after having found that lars matches the query. If there would have been other programmers defines, Prolog would reply with the prompt ?
after having displayed X = lars
. If one then presses the ;
key, the next programmer would have been printed and so on until all programmers would have been displayed.
Let's define another programmer and run the query again:
occupation(lars, programmer).
occupation(per, programmer).
likes_coca_cola(X) :- occupation(X, programmer).
...
| ?- likes_coca_cola(X).
X = lars ? ;
X = per
yes
| ?-
As mentioned above, atoms are things that are constant. They are not like constants in many other languages that have names and values. An atoms does not have a value other than its name. In that sense, an atom is more like a symbol i Ruby.
Also as mentioned above, variables are things that may take different values. They are specified in the parameter list of rules and can be used in the body of the rule which parameter list they are part of.
Variables can also be used in a query. In that case, Prolog tries to find all values that the variable legally could take given the defined facts and rules, and prints out those values.
Unification is a process in which Prolog tries to find values for the variables in two structures so that the structures become equal. When we issue the query likes_coca_cola(X).
above, Prolog tries to find values to the variable X
so that the rule likes_coca_cola(X) :- occupation(X, programmer).
will be fulfilled. As we have defined the facts occupation(lars, programmer)
and occupation(per, programmer)
, the valid values which Prolog finds are lars and per. The process which Prolog uses to find these matches is called unification.
Comments are created using the /* This is a comment.*/
syntax.
Tuples are fixed-length datastructures. They are created using parenthesis: (1,2,3)
.
Two tuples unifies if they have the same length and each of the corresponding elements unify. E.g. (1,2,3) = (1,2,3)
Variables can be used when unifying tuples and it does not matter which side of the equals sign that the variables are located on.
| ?- (X,2,Z)=(1,Y,3).
X = 1
Y = 2
Z = 3
(1 ms) yes
We see that (as in all unification) Prolog tries to make both of the structures on the opposite sides of the equal sign equal and finds the solution X=1, Y=2 and Z=3.
Lists are datastructures that can hold a variable number of items. Lists are created using brackets: [1,2,3]
and are unified in the same manner as tuples:
| ?- [X,2,Z]=[1,Y,3].
X = 1
Y = 2
Z = 3
yes
An interesting thing with lists is that they can be deconstructed in a head part consisting of the first element and a tail part containing the rest of the list. Such deconstruction can be by unifying a list using the [Head|Tail]
construct. Of course, the variable names are arbitrary. An example of deconstructing a list:
| ?- [1,2,3]=[Head|Tail].
Head = 1
Tail = [2,3]
yes
If the list only contains one element, the tail unifies to the empty list:
| ?- [1]=[Head|Tail].
Head = 1
Tail = []
yes
When unifying lists or tuples, an underscore can be used as a wild card to indicate that an element of a specific position or a tail is not interesting. This is useful when one wants to access some other position in a list but the first. An example: to access the second element in a list we may do the following:
| ?- [1,2,3]=[_,X|_].
X = 2
yes
Here we say that we are not interested in the first element but please assign the second element to the variable X, and by the way, we're not interested in the rest of the list (the tail) either.
A rule can recursively call itself. To make that work, one needs to have a non-recursive base rule where the recursive calls will end up. Then we can have other rules of the same name that in addition to performing a recursive call also performs some other action that changes the params used in the recursive call. Other actions can of course also be performed, both before and after the recursive call.
One of the standard problems when discussing recursion is computing a Fibonacci number. So let's try that. A naive implementation of this problem in Prolog can look like this:
fibonacci(0, 1).
fibonacci(1, 1).
fibonacci(N, Result) :-
N1 is N - 1,
N2 is N - 2,
fibonacci(N1, R1),
fibonacci(N2, R2),
Result is R1 + R2.
Listing 1: Naive implementation of Fibonacci number calculation.
In the program two facts and one rule is defined. First we have the base cases: the first Fibonacci number == 1 and the second Fibonacci number == 1. Then we have the general case which is taken care of by defining a rule that calls itself, or one of the base cases, recursively.
We see that the rule, and the facts. takes two parameters: the ordinal deciding which Fibonacci number to calculate and the calculated value. Unification then takes care of making the two sides of the rule equal and after that has been performed, the result can be found in the Result
variable.
To me, this feels kind of strange. Result
acts as an output parameter in some other languages (like Ada, if I remember correctly), but I'm not sure that is the correct way of thinking. Or rather, I'm certain that this is not the way to think about it. I think that a more proper way to think about it is that unification makes the two sides of the rule equal, and afterwards, as a convenient side-effect, the Result
parameter will contain the result that one wanted to obtain. Prolog is a peculiar language.
I the listing above the is
operator has been used. It is used when performing arithmetic, and it acts like an assignment operator. Of course, it is not described in this way in the documentation I've found. In there, its workings is described as "first the right hand side is evaluated, then the left hand side and right hand side are unified".
My first attempt of writing a Fibonacci program in Prolog did not include the variables N1
and N2
. Instead I tried to pass N - 1
and N - 2
directly in the recursive call to fibonacci()
, This, however, did not work. Right now, I don't know why. It most certainly has a logical explanation. It's Prolog after all.
Is there a problem with the problem with the program in listing 1 above? Yes, it is. The recursive call is not the last statements in the rule, I.e. the rule is not tail recursive. Why is this a problem then? It is a problem because when performing very many nested recursive calls, the stack of the program may run out of space, which makes the program terminate with an exception. If the program is written in a tail-recursive manner, a modestly potent interpreter/compiler can rearrange the program and create an iterative variant from the recursive one, and by doing that, avoid running out of stack space.
So, how do we create a tail-recursive variant of the Fibonacci program above? Well, if we start calculating the first and second Fibonacci number, we can use those numbers to calculate the the third number and so on. So it we add two more parameters to the fib()
rule, where the first is the i:th Fibonacci number and the second is the (i+1):th Fibonacci number we can use those to calculate the (i+2):th number, which then can be passed to the next recursive call (together with the (i+1):th). This means that when we have perfomed n iterations, we have the answer in the first of those new parameters, namely the n:th Fibonacci number.
The rule can look like this:
fib(DownCounter, ValueForN, ValueForNPlus1, FinalResult) :-
NextDownCounter is DownCounter - 1,
ValueForNPlus2 is ValueForN + ValueForNPlus1,
fib(NextDownCounter, ValueForNPlus1, ValueForNPlus2, FinalResult).
Listing 2: Rule for calculating Fibonacci numbers tail-recursively.
Pretty straight forward, eh? But what's the FinalResult
parameter we just passes through to the recursive invocations? It is the parameter in we will put the final result. But it it not used? That's because we haven't defined the base case yet (which means that the recursion will continue forever, or at least until we get a stack overflow exception). So let's define the base case.
We have performed n recursive calls when the DownCounter
reaches 0. Thus, we create a case where we put 0 instead of DownlinkCounter
. Then we said that we had the result in the first new parameter, ValueForNPlus1
. The second new parameter, we're not really interested in as it contains the (n+1):th Fibonacci number, so let's make it clear that we don't care about that value by putting an underscore in that position instead of ValueForNPlus1
. The fourth and final parameter is where we want to put the result. The base case thus becomes:
fib(0, FinalResult, _, FinalResult).
Listing 3: Base case for the tail-recursive Fibonacci program.
We could be finished here. For example, to calculate the sixth Fibonacci number we could invoke the rule like this
| ?- fib(6, 0, 1, FinalResult).
FinalResult = 8 ?
Listing 4: Invoking the fib/4 rule to calculate the sixth Fibonacci number.
Is is however possible to make a slight simplification. If we use the fib/4 rule directly, we always have to specify 0 as the second parameter and 1 as the third parameter. We therefore create a fib/2 rule that only hard-codes these values:
fib(DownCounter, FinalResult) :- fib(DownCounter, 0, 1, FinalResult).
Listing 5: Simplifying rule for starting the tail-recursive Fibonacci program.
Using this rule, we can calculate the sixth Fibonacci number like this:
| ?- fib(6, FinalResult).
FinalResult = 8 ?
Listing 6: Using the fib/2 rule to calculate the sixth Fibonacci number.
The full tail-recursive Fibonacci program looks like this:
fib(0, FinalResult, _, FinalResult).
fib(DownCounter, ValueForN, ValueForNPlus1, FinalResult) :-
NextDownCounter is DownCounter - 1,
ValueForNPlus2 is ValueForN + ValueForNPlus1,
fib(NextDownCounter, ValueForNPlus1, ValueForNPlus2, FinalResult).
fib(DownCounter, FinalResult) :- fib(DownCounter, 0, 1, FinalResult).
Listing 7: Full tail-recursive Fibonacci program.
A simple hello world program (are there any others?) can look like this in Prolog:
helloWorld :- write('Hello World!'), nl.
Listing 8: Hello World in Prolog.
When invoked, the output looks like this:
| ?- helloWorld.
Hello World!
yes
Listing 9: Output when running the Hello World program..
Unification. It is so engraved into the language that it hardly can be called a feature. But I do it anyway ;) It it immensely powerful for certain kinds of problems.
As a small demonstration of the power of Prolog and unification, let's write a small program that can solve a sudoku.
- Using := instead of :- when starting a rule.
- Fail to remember to end each fact or rule with a dot.
- Using an atom instead of a variable when performing a query. I.e. starting a word with a lowercase letter when an uppercase letter should have been used.
An implementation of the FizzBuzz kata in Prolog can look like this:
/* Start at 1.*/
fizzBuzz :- fizzBuzz(1).
/* When we reach 101, we stop. */
fizzBuzz(101).
/* Rule resolving to true for numbers dividable by both 3 and 5. */
fizzBuzz(N) :-
X is N mod 3,
Y is N mod 5,
X == 0,
Y == 0,
writeAndPerformNextCall(N, 'FizzBuzz').
/* Rule resolving to true for numbers dividable by 3 but not 5. */
fizzBuzz(N) :-
X is N mod 3,
Y is N mod 5,
X == 0,
Y \= 0,
writeAndPerformNextCall(N, 'Fizz').
/* Rule resolving to true for numbers dividable by 5 but not 3. */
fizzBuzz(N) :-
X is N mod 3,
Y is N mod 5,
X \= 0,
Y == 0,
writeAndPerformNextCall(N, 'Buzz').
/* Rule resolving to true for numbers not by neither 3 nor 5. */
fizzBuzz(N) :-
X is N mod 3,
Y is N mod 5,
X \= 0,
Y \= 0,
writeAndPerformNextCall(N, N).
/* Rule that prints the specified text and performs the next recursive call with the next integer in the series.*/
writeAndPerformNextCall(N, Txt) :-
write(Txt),
nl,
NPlus1 is N + 1,
fizzBuzz(NPlus1).
Listing 10: FizzBuzz kata in Prolog.
In the program above, we've defined one rule for each of the four cases:
- Number dividable by both 3 and 5.
- Number dividable by both 3 but not 5.
- Number dividable by both 5 but not 3.
- Number not dividable by 3 or 5.
We've also created a rule that starts the recursive calls and one fact that stops the recursion. The printing of output to stdout and the invocation of the next call in the recursive chain is done by the rule writeAndPerformNextCall
.
I think that the FizzBuzz program in Prolog is quite neat. Four rules govern the four cases that can happen, and recursive calls take us to the next number in the series. It was not very easy for me to reach the solution above however. I must say that the Prolog-version of the FizzBuzz-kata was the one that, by far, has taken me the longest time to write. Otherwise, the Kata is very simple and straightforward to write. The time it took at lot longer to find an acceptable solution. I think that this is so because I'm very unused to thinking "The Prolog Way".
Solutions to certain kinds of problems, like solving a Sudoku, can be expressed extremely beautifully in Prolog. Once one understands unification that is. Such programs often become short, concise and to the point. Sometimes seeing a Prolog program producing a solution to a problem feels like magic.
Also on the plus side, there is a lot of Prolog related material on the Net, much, much more than about Io.
Prolog has a steep learning curve. It is not easy for a beginner to learn the language. If one checks out some Prolog programs written by more experienced Prolog programmers, those are often terse and very hard to understand.
Also, Prolog does not feels like an all-purpose programming language. It is aimed at logic programming, for which it is extremely well suited, and when one has another type of problem, one should perhaps choose another language. This is not criticism against Prolog. It does what what it was build for very well.
It was fun to play around with Prolog! I'm not certain, however, that I ever will use it in a work related context. Now its time for a language which I have a fair chance of using at work. At least in a couple of years from now. Scala!
1. Ruby
2. Io
[1] Prolog Programming Guide
2015-06-30
Scala is a strongly and statically typed hybrid language. It manages to be both an object-oriented language and a functional language at the same time. It is based on Java.
Scala runs on the JVM and, thus, can use Java libraries directly.
Scala programs can be executed by the scala
interpreter bundled with the Scala Development Kit.
When starting up the scala
interpreter, the following prompt pops up scala>
At that prompt, Scala programs can be entered. If the interpreter seems to hang after some piece of a Scala program has been entered, just have a little bit of patience. The interpreter is quite slow.
The second way that Scala programs can be executed is by supplying the name of a file containing a Scala program to the scala
executable when running it from the command line. E.g.
lars@lars-lilla:$ scala helloWorld.scala
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar
Hello World!
lars@lars-lilla:$
Scala programs can also be compiled to Java bytecode and executed by the JVM. The command to compile a Scala program is logically called scalac
. It produces one or more .class
files. The class
file containing the main
method can then be executed by the scala
program.
lars@lars-lilla:$ scalac HelloWorld.scala
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar
lars@lars-lilla:$ scala -cp . HelloWorld
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar
Hello, World!
As you can see, I had to supply the -cp .
arg to scala
to tell it to look for classes in the current dir (as . is not on the CLASSPATH
).
Semicolons are optional.
Comments in Scala are created using the //
, /* */
or /** */
styles. The last form is called scaladoc and is similar to javadoc in Java.
Hello World in Scala can look like this:
println("Hello World!")
Listing 1: Hello World in Scala.
A variable is defined using the val
keyword or the var
keyword. The difference between them is that variables defined using val
are immutable, variables defined using var
are not.
Example:
scala> val i = 1
i: Int = 1
scala> var j = 20
j: Int = 20
scala> j = 30
j: Int = 30
scala> i = 2
<console>:8: error: reassignment to val
i = 2
^
Scala tries, if possible, to guess which type a variable has. This is done at compile time and is called type inference. You can explicitly declare the type of a variable by appending it after the variable name separated by a comma. E.g. val b : Int = 2
Arrays are costructed using the following syntax: val arrayOne : Array[String] = new Array[String](3)
The compiler can most often infer the type of the array, which means that the example above could have been written like this: val arrayOne = new Array[String](3)
It is also possible to initialize the elements at construction time: val arrayTwo = Array("One", "Two", "Three")
To retrieve an element of an array the index at which the desired element resides is specified in regular parenthesis:
val arrayTwo = Array("One", "Two", "Three")
println("The second element of the array is: " + arrayThree(1))
...
The second element of the array is: Two
Listing 2: Accessing an element in an array.
The same syntax is used when assigning an individual element of an array:
val arrayThree = Array("One", "Two", "Three")
arrayThree(1) = "Four"
println("The second element of the array is now: " + arrayThree(1))
...
The second element of the array is now: Four
Listing 3: Assigning to an element in an array.
Note that we could assign to the index in the array despite the variable pointing to the array being a val
. It is only forbidden to reassign the variable pointing to the array, not updating the array itself.
In Scala it is also possible to create multi-dimensional arrays using the ofDim()
method from the Array
class.
import Array._
val multiDimArray = ofDim[String](2, 3)
multiDimArray(0)(0) = "a"
multiDimArray(0)(1) = "b"
multiDimArray(1)(0) = "c"
println("Accessing an element in the multi-dimenstional array: " + multiDimArray(0)(1)
Listing 4: Multi-dimensional arrays.
Tuples are created by enclosing a comma separated list of values in parenthesis: var tpl = ("Hello", "World")
The elements of a tuple can be accessed using the ._x
syntax, where x is a number between 1 and the length of the tuple. An example:
var tpl = ("Hello", "World")
println("The first item of the tuple is: " + tpl._1)
println("The second item of the tuple is: " + tpl._2)
...
The first item of the tuple is: Hello
The second item of the tuple is: World
Listing 5: Using tuples.
It is an error to assign to a to a tuple once it has be created or to try to access an index greater than the length of the tuple. Such errors are caught at compile time.
Multivariable assignment in Scala can be done using tuples:
val (a, b) = ("A", "B")
println("a = " + a)
println("b = " + b)
...
a = A
b = B
Listing 6: Multivariable assignment.
A List
is an immutable implementation of a linked list. It it meant to to be operated on only at the head the end. It is possible to access the elements in the middle of the list, but not reassign them.
Lists can, like tuples be created in a number of ways. One way if to use the List
constructor: val aList : List[String] = List("a", "b", "c")
. Another way is to use cd cons construction ::
. Cons separates the head value of a list from the tail of the list, it but can also be used to create a list. Like this: val integerList = 1 :: (2 :: (3 :: (4::Nil)))
. Note that Nil
is equal to an empty list.
To access an element in the firs element in a list, the head
method can be used:
println("The first item of the list is: " + integerList.head)
...
The first item of the list is: 1
Listing 7: Using head to access the first element in a list.
Retrieval of an element in a specific posistion in the list can be performed using the syntax list(position)
where 0 represents the first element in the list, 1 the second and so forth. E.g.
println("The first item of the list is: " + integerList(0))
println("The second item of the list is: " + integerList(1))
println("The third item of the list is: " + integerList(2))
println("The fourth item of the list is: " + integerList(3))
...
The first item of the list is: 1
The second item of the list is: 2
The third item of the list is: 3
The fourth item of the list is: 4
Listing 8: Accessing elements in all positions in a list using ().
As lists are immutable, you cannot add or remove anything from it. It it however possible to create a new list that is based on the old list but has an element added at the front or at the end. The ::
operator can be used to add an element the the head of a list and the :+
operator can be used to add to the end.
val integerList2 = 0 :: integerList
println("The first item of the second list is: " + integerList2(0))
val integerList3 = integerList :+ 5
println("The last item of the third list is: " + integerList3(4))
...
The first item of the second list is: 0
The last item of the third list is: 5
Listing 9: Forming new lists by appending or prepending elements to an old list.
Note that this creation of new lists is not as inefficient as it seems. This is so because Scala shares memory between the original list and the new lists.
To extract a list containing all elements but the first of an original list, the tail
method can be used:
println("All but the first element of the third list: " + integerList3.tail)
...
All but the first element of the third list: List(2, 3, 4, 5)
Listing 10: Using tail
The isEmpty()
method checks if a list is empty or not.
println("Is integerList3 empty? " + integerList3.isEmpty)
println("Is a List() empty? " + List().isEmpty)
...
Is integerList3 empty? false
Is a List() empty? true
Sometimes it is desired to work on a mutable collection. Both ArrayBuffer
and ListBuffer
are such collections. Both live in the scala.collection.mutable
package. The difference between them is that ArrayBuffer
is backed by an array but ListBuffer
is backed by a list. The difference is mainly important if you want to convert your collection into an array or a list.
An example:
import scala.collection.mutable.ArrayBuffer
val arrayBuffer1 = ArrayBuffer.empty[String]
// += can be used to append an element to the end of the buffer.
arrayBuffer1 += "a"
println("First element: " + arrayBuffer1(0))
// As can append().
arrayBuffer1.append("b")
println("* First element: " + arrayBuffer1(0))
println("* Second element: " + arrayBuffer1(1))
// Modify the last element.
arrayBuffer1(1) = "B"
println("** First element: " + arrayBuffer1(0))
println("** Second element: " + arrayBuffer1(1))
// Remove the first element.
arrayBuffer1.remove(0)
println("*** First element: " + arrayBuffer1(0))
...
First element: a
* First element: a
* Second element: b
** First element: a
** Second element: B
*** First element: B
Ranges can be created using the until
or to
keywords. The difference between them is that to
is inclusive and to
is exclusive. A step can be specified using the by
keyword.
val rangeOne = 1 until 5
val rangeTwo = 1 to 5
val rangeThree = (1 to 5) by 2
val rangeFour = (5 to 1) by -2
Listing 11: Examples of ranges.
Maps can be created using either the Map
keyword or using the regular new Map()
syntax. Scala uses ->
to specify a key-value-pair and () to access the elements. Here is a small example program demonstrating the most basic Map operations:
// Create an (immutable) Map using the Map keyword.
val aMap = Map("a"->"the first letter", 42 -> "Meaning of life")
println(aMap)
// Create a (mutable) Map by importing HashMap and creating a new instance of it.
import scala.collection.mutable.HashMap
val bMap = new HashMap[Int, String]
bMap += 1 -> "First"
bMap += 2 -> "Second"
println(bMap)
// Extract value for a specific key.
println("The value of the 1 key is: " + bMap(1))
// Change value for key.
println("Changing value of for the 2 key to Seven")
bMap(2) = "Seven"
println("The value of the 2 key is now: " + bMap(2))
...
Map(a -> the first letter, 42 -> Meaning of life)
Map(2 -> Second, 1 -> First)
The value of the 1 key is: First
Changing value of for the 2 key to Seven
The value of the 2 key is now: Seven
Listing 12: Creating maps, adding elements and retrieving values.
As with lists, there are both mutable and immutable versions of Sets. By default, the immutable variant is imported into Scala programs (by means of the Scala package object, see below). If you want to have a mutable set, use import scala.collection.mutable.Set
.
The Set
class supports all kinds of operations, e.g. union, intersection and difference. A small example program exercising a (mutable) Set
is shown below.
import scala.collection.mutable.Set
println("------------- aSet ------------")
val aSet = Set("one", "two")
println(aSet)
println("---------Add stuff to aSet---------------")
aSet += "three"
println(aSet)
println("------------- bSet ------------")
val bSet = Set("two", "four")
println(bSet)
println("---------- UNION ---------------")
var unionSet = aSet ++ bSet
println(unionSet)
println("---------- DIFFERENCE ---------------")
var differenceSet = aSet -- bSet
println(differenceSet)
...
------------- aSet ------------
Set(two, one)
---------Add stuff to aSet---------------
Set(three, two, one)
------------- bSet ------------
Set(four, two)
---------- UNION ---------------
Set(four, three, two, one)
---------- DIFFERENCE ---------------
Set(three, one)
Listing 13: Set example program.
An if-clause in Scala is written like this:
var i = 42
if (i < 0) {
println("Less than 0")
} else if (i <= 100) {
println("Between 0 and 100.")
} else {
println("Greater than 100.")
}
Listing 14: An if-clause.
Note that if clauses really are if expressions in Scala. Every if
will return a single value. As an example, take a look at the following method that switches between the two players in a Tic Tac Toe game:
class Game {
var activePlayer = PLAYER_1
...
def nextPlayer() : PlayerType = {
return if (activePlayer == PLAYER_1) PLAYER_2 else PLAYER_1
}
Listing 15: An if-clause used as an expression.
The last statement in the selected branch in the if - else if - else
clause becomes the value of the if-expression.
A while loop can look like this:
var i = 0
while (i < 5) {
println(i)
i += 1
}
Listing 16: A while loop.
A for loop has the following syntax:
for (i <- 0 until 4) {
println("This is iteration: " + i)
}
Listing 17: A for loop.
Scala also supports for-each-loops. Example of one such:
val anArray : Array[String] = Array("One", "Two", "Three")
anArray.foreach { item =>
println(item)
}
Listing 18: A foreach loop.
A function is defined using the def
keyword. If the function does not take any parameters, the parenthesis can be omitted. If they are omitted, however, you must be careful not to include them when the function is called. If you do include them when calling the function, the compiler will complain. A rather peculiar thing is that if you include the parenthesis when defining a function that does not take any parameters, then you must include them when calling the function. That does seem logical. I think. At any rate, I'm sure there's a good explanation. I'm sure of it. Almost.
An example of two methods that does not take any arguments:
// Define two methods that don't take any arguments.
def printGreeting {
println("Hello, friend!")
}
def printGreeting2() {
println("Hello, friend2!")
}
// Call them.
printGreeting
printGreeting2()
Listing 19: Two no-arg functions.
The type of a parameter is specified after the name: anIntegerParam : Int
. If the function return a value, the type of the returned value is specified after the parameter list:
def aFunctionReturningSomething() : Int = return 42
Listing 20: A function returning a value.
A main
method of a Scala program is created like this:
def main(args : Array[String]) {
//...
}
Listing 21: A main-method.
The command line arguments can be accessed from the args
array. Element 0 will contain the first argument, element 1 the second one and so on.
package testPackage
object MainTest {
def main(args : Array[String]) {
for (j <- 0 until args.length) {
println("Argument " + j + " is " + args(j))
}
}
}
Listing 22: Accessing the command line args.
Scala has a construct similar to switch
in Java and C, but a little bit more powerful. To match a variable against a list of fixed values you write
aVariable match {
case "firstCase" => doStuffForCaseA
case "secondCase" => doStuffForCaseB
case _ => doStuffForDefaultCase
}
Listing 23: Syntax of the match
expression.
We see that the underscore character is used as a wildcard matching everything that the other cases didn't catch.
As an example, let's write a program that matches the first command line argument against a number of fixed strings and prints an appropriate message.
def main(args : Array[String]) {
args(0) match {
case "hi" => println("Hi!, Hi!")
case "hopp" => println("Hopp, hopp")
case _ => println("A good day to you took, Sir!")
}
}
...
lars@lars-lilla:~/programmering/14languages/scala/day3$ scala -cp . matchTest.MatchTest hi
Hi!, Hi!
lars@lars-lilla:~/programmering/14languages/scala/day3$ scala -cp . matchTest.MatchTest hopp
Hopp, hopp
lars@lars-lilla:~/programmering/14languages/scala/day3$ scala -cp . matchTest.MatchTest Good day!
A good day to you took, Sir!
Listing 24: Using match
to match input arg against a set of strings.
The class
keyword is used to define a class. If the class should contain attributes only (and thus be a Data Transfer Object, Value Object or whatever you wish to call that particular design pattern), you don't have to specify a body when defining the class. The name and types of the attributes is sufficient.
package dtos
class PairOfStrings(val first : String, val second : String)
Listing 25: A class without a body.
The class can be used like this:
package testPackage
import dtos.PairOfStrings
object PairTest {
def main(args : Array[String]) {
val pair = new PairOfStrings("one", "two")
println("The first item in the pair is: " + pair.first)
println("The second item in the pair is: " + pair.second)
}
}
...
The first item in the pair is: one
The second item in the pair is: two
Listing 26: Program using the body-less class.
Now to something that I find a little peculiar about Scala: constructors. If you want to include a constructor into a class, which you in my opinion want to do fairly often, you just type expressions and statements after the row containing the class
keyword. As an example, consider this simple class modeling a person:
package dtos
class Person(firstName : String, lastName : String) {
val fullName = firstName + " " + lastName
println("Inside the primary constructor.")
def getFirstName() = firstName
def getLastName() = lastName
def printName() {
println(fullName)
}
}
Listing 27: Simple person class.
We see that we defined a field called fullName
inside the constructor. We also print out the full name of the Person on std out. Both of these lines are part of the constructor. In fact, also the method definitions that come after are also part of the primary constructor. Very confusing. Yes, indeed.
In the example above, we also see the simplified syntax that Scala allows for a one-line method: def methodName() = body
. Scala will return the value of the last expression in the method, which in this case results in the value of the firstName
and lastName
fields being returned from the getFirstName()
and getLastName()
methods respectively.
Auxiliary constructors can be added to a class by adding a def this(params...)
definition for each such constructor that it is desired to add. We can for example add an initial letter and an auxiliary constructor in which it is set to the Person
class above.
package dtos
class PersonWithInitial(firstName : String, lastName : String) {
val fullName = firstName + " " + lastName
var initialLetter = ""
println("Inside the primary constructor.")
def this(firstName : String, lastName : String, initialLetter : String) {
this(firstName, lastName)
this.initialLetter = initialLetter
}
def getFirstName() = firstName
def getLastName() = lastName
def getInitialLetter() = initialLetter
def printName() {
println(fullName)
}
}
Listing 28: Person class where the person also can have an initial letter.
Note that the primary constructor is invoked from the auxiliary constructor using this(args...)
.
The new class can for example be used like this:
package testPackage
import dtos.PersonWithInitial
object PersonWithInitialTest {
def main(args : Array[String]) {
val adam = new PersonWithInitial("Adam", "Smith", "B")
println("The first name is: " + adam.getFirstName())
println("The last name is: " + adam.getLastName())
println("The initial letter is: " + adam.getInitialLetter())
print("The full name is: ")
adam.printName()
}
}
...
Inside the primary constructor.
The first name is: Adam
The last name is: Smith
The initial letter is: B
The full name is: Adam Smith
Listing 29: Program using the auxiliary constructor in PersonWithInitial.
Some other things about classes:
- The default access modifier is
public
.
- Constructor params is by default
val
What about static stuff? How do you add such things to a class? It turns out that you don't. Instead you create a companion object to the class. In that you put your static fields and methods. So how is such a companion object created? Like a regular class, except that the keyword object
is used instead of class
. For example, to create a companion object to the Person
class above, the following code could be used:
object Person {
var counter = 0
def incCounter() = counter += 1
def getCounter() = counter
}
Listing 30: Companion object to the Person class.
The companion object of a class must be located in the same file as the class itself. In our example the file contents of the Person.scala file looks like this:
package dtos
class Person(firstName : String, lastName : String) {
// The following does not work as constructor params by default is val.
//firstName = "Hej"
val fullName = firstName + " " + lastName
println("Inside the primary constructor.")
def getFirstName() = firstName
def getLastName() = lastName
def printName() {
println(fullName)
}
}
object Person {
var counter = 0
def incCounter() = counter += 1
def getCounter() = counter
}
Listing 31: Contents of the Person.scala file.
A small program that uses the Person
companion object can look like this:
package testPackage
import dtos.Person
object PersonWithStaticStuff {
def main(args : Array[String]) {
dtos.Person.incCounter()
println("Value of the counter in person is: " + dtos.Person.getCounter())
val adam = new dtos.Person("Adam", "Smith")
// This does not work:
//adam.incCounter()
}
}
Listing 32: Test program of the companion object to the Person class.
As the comments in the example program shows, it is not allowed to access static content through an instance variable, something that is legal, but frowned upon, in Java.
It is of course possible to define a main method inside a companion object, something that will be used in an example in the next section.
A class in Scala can inherit from exactly one parent class. The extends
keyword is used to specify which class to inherit from.
When defining a class inheriting from another class, the constructor of the class that is inherited from must be called by the primary constructor of the inheriting class. The override
keyword must be used when defining constructor parameters of the same name as those in the parent class.
The override
keyword is also used when overriding methods from the parent class. An example:
package dtos
class Programmer(override val firstName : String, override val lastName : String, val favouriteProgrammingLanguage : String) extends Person(firstName, lastName) {
val this.favouriteProgrammingLanguage = favouriteProgrammingLanguage;
def getFavouriteProgrammingLanguage() = favouriteProgrammingLanguage
override def printName() {
println(fullName + " : " + favouriteProgrammingLanguage)
}
}
object Programmer {
def main(args : Array[String]) {
val lars = new Programmer("Lars", "Nohle", "Python")
lars.printName()
}
}
...
Inside the primary constructor.
Lars Nohle : Python
Listing 33: Example of class inheritance.
In the example above we define a new class Programmer
that inherits from class Person
that we defined in an earlier section. We've added another constructor parameter to the existing two from Person
. The constructor params that we must pass to the super-constructor in Person
need to be tagged with override
. We've also overridden the printName()
method, which means that that method also needs to marked with the override
keyword.
Note that we've added a companion object defining the main
method to the file containing the Programmer
class. In main
, we've added a small code snippet exercising the Programmer
class.
A trait is a collection of methods (contained in a file) that can be added to a class. A trait is similar to a mixin but with the difference that a trait cannot contain state. The reason for adding a trait to a class is to add new behaviour to the class without adding an is-a
relationship.
So how is a trait defined in Scala? It is (surprise!) defined using the trait
keyword. A class that want to use a trait does that by using the with
keyword if the class extends another class and using the extends
keyword if the class does not extend another class.
As an example, let's define a trait called CheckpointLoggable
that contains methods that can be used to send log messages to a centralized logging application.
package testPackage
trait CheckpointLoggable {
def log(msg : String) {
println("Sending the following message to the central logger: " + msg)
// Send message to the central logger.
//...
}
}
Listing 34: A trait that provides access to simple centralized, logging functionality.
The CheckpointLoggable
trait can now be used in a class like this:
package testPackage
class ImportantService extends Service with CheckpointLoggable {
// Many important methods...
}
object ImportantService {
def main(args : Array[String]) {
val service = new ImportantService()
service.log("testing...")
}
}
...
Sending the following message to the central logger: testing...
Listing 35: Class (inheriting from another class) that uses the CheckpointLoggable trait.
As in previous examples, the main method exercising the class in included in the companion object of the class.
If the class wanting to mix-in the trait wouldn't have inherited from another class, the syntax would have been like this:
package testPackage
class ImportantService extends CheckpointLoggable {
// Many important methods...
}
Listing 36: Class not inheriting from another class that uses the CheckpointLoggable trait.
The XML support in Scala is very, very good. You can enter XML code directly in Scala code and assign it to a variable. That variable can then be used to process the XML data in various ways.
val books =
<books>
<book genre="Computer languages">
<title>Seven Languages in Seven Weeks</title>
<author>Bruce A Tate</author>
</book>
<book genre="Best practises">
<title>Effective Java</title>
<author>Joshua Bloch</author>
</book>
</books>
Listing 37: Assigning XML code to a variable.
All free text contained in the XML snippet can be extracted using the text
method.
println(books.text)
...
Seven Languages in Seven Weeks
Bruce A Tate
Effective Java
Joshua Bloch
Listing 38: Extracting free text from an XML block..
Scala also supoorts an XPath-like syntax for extracting nodes and attribute values from an XML block. To extract from the top node only \
is used:
val bookSequence = books \ "book"
Listing 39: Extracting subelements of the top level element...
The returned nodes can then be access by position or in sequence using for example foreach
:
// Fetch the first book.
println("The first books is: " + bookSequence(0))
// Loop through the books.
println("The books:")
bookSequence.foreach {book =>
println("Book: " + book.text)
}
...
The first books is:
Seven Languages in Seven Weeks
Bruce A Tate
The books:
Book:
Seven Languages in Seven Weeks
Bruce A Tate
Book:
Effective Java
Joshua Bloch
Listing 40: Accessing the extracted elements.
To extract nodes from the whole XML document, not only from the top node, \
is used:
// Extract and print the titles of the books.
val titlesSequence = books \ "title"
println("The titles of the books are: ")
titlesSequence.foreach {title => println(title.text)}
...
The titles of the books are:
Seven Languages in Seven Weeks
Effective Java
Listing 41: Extracting elements from the whole document.
To retrieve attribute values, @
is used in the query:
// Extract and print the genres.
println("The genre attributes of the book elementa are; ")
val genresSequence = books \ "@genre"
genresSequence.foreach {genre => println(genre)}
...
The genre attributes of the book elementa are;
Computer languages
Best practises
Listing 42: Extracting attribute values.
Scala strings have a nice method called r()
that translates a string to a regular expression of type scala.util.matching.Regex
.
The scala.util.matching.Regex
contains a lot of useful methods. Using that it is possible to performing the usual regexp stuff that we all love to perform, e.g. searching, replacing and grouping. Searching can be performed by one of the various findXXX()
methods (findFirstIn()
, findAllIn()
, ...), replacing with replaceXXX()
(replaceFirstIn()
, replaceAllIn()
, ...).
The following program is an example of using regexps in Scala:
import scala.util.matching.Regex
def testIfMatches(pattern : Regex, stringToExamine : String) {
if (pattern.findFirstIn(stringToExamine) != None) {
println("'" + stringToExamine + "'" + " matches")
} else {
println("'" + stringToExamine + "'" + " don't match")
}
}
val pattern = "^(h|H)ello".r
testIfMatches(pattern, "Hello")
testIfMatches(pattern, "hello")
testIfMatches(pattern, "A Hello")
...
'Hello' matches
'hello' matches
'A Hello' don't match
Listing 43: Using a regexp to perform matching.
Package objects something that I've not encountered in another language. In Scala, each package can have zero or one package objects in which various definitions can be placed. These definitions will then automatically be part of every member of the package. It is also possible to import these definitions into other Scala programs using the syntax import packageWithPackageObject._
.
A package object is defined in a file named package.scala
. That file should have a package
that includes all parent packages of the package in question, but not the package itself. The name of the package is instead specified in the package object
statement in which the content that should be shared with all members of the package is defined.
As a simple example, let's create a package object for the package apkg
. The package object should only contain a single integer constant. As the apkg
package does not have any parent packages, the package object should not contain any package
statement.
// In file apkg/package.scala
package object apkg {
val A_CONSTANT = 42
}
Listing 44: Simple package object.
To demonstrate the that all members of the package apkg
get access to the A_CONSTANT
we create a small test program that just prints the value of that constant.
package apkg
object ATestProgram {
def main (args : Array[String]) {
println("The value of the constant is: " + A_CONSTANT)
}
}
...
The value of the constant is: 42
Listing 45: Using the declarations in a package object from a object in the same package..
- A public class in Scala can have a different name compared to the file it is located in.
- The names of many common datatypes is different from those in Java: Int, Boolean, ...
for (i <- 1 until 101) {
if (i % 3 == 0 && i % 5 == 0) println("FizzBuzz") else if (i % 3 == 0) println("Fizz") else if (i % 5 == 0) println("Buzz") else println(i)
}
Listing 46: FizzBuzz in Scala. Using a too long line...;)
- The syntax for creating one line methods:
def f() = body
- The syntax for getting and setting values in maps:
aMap(key) = value;val v = aMap(key)
- The XML support. Is it astonishing!
- Compilation and interpretation of Scala programs are slow. Really slow. I can't remember any other language for which compilation and interpretation are so slow.
- The keyword that should be used to mix in a trait is different when the class wanting to use the trait inherits from another class (
with
) and when it does not inherit from another class (extends
). Why do this? It's just adds unnecessary confusion.
That was Scala. Many nice features it has, but now it's time for something entirely different: Erlang! Auf wiedersehen!
1. Ruby
2. Io
3. Prolog
[1] Scala Development Kit Download
[2] Scala Homepage
[3] Scala Lists
[4] Scala Sets
[5] Scala Maps
2015-08-30
Erlang is a functional language built to handle concurrency very well. It is not a pure functional language, however. Some non-functional things are possible to do.
Erlang is dynamically typed. It tries to infer the types of variables from the context of the variable. It is also strongly typed: a mismatch in types will generate an error at runtime.
The language is heavily influenced by Prolog. This will be rather apparent when we go through the its syntax.
Erlang programs can be run in the interpreter provided by the development kit. To do so, just invoke the erl executable. A prompt like this will appear:
Eshell V6.2 (abort with ^G)
1>
Listing 1: The prompt of the Erlang shell.
At the prompt, Erlang programs can be entered. E.g.
1> 1 + 2.
3
2>
Listing 2: Entering a program at the prompt of the Erlang shell.
If you don't want to use the shell, Erlang programs can be compiled by supplying -compiled
command line arg to erl
. Compiling a file containing an Erlang program (which should have an .erl
extension by convention) will produce a .bean
file. The Erlang program can then be executed using the -noshell
argument to erl
. For example, if you have a program in the file first_program.erl that looks like this
-module(first_program).
-export([first/0]).
first() ->
io:fwrite("Then first Erlang program!").
Listing 3: Program to exemplify compiling.
Then it can be compiled by issuing the following command: erl -compile first_program.erl
. The program can then be executed using this command line: erl -noshell -s first_program first -s init stop
...
Then first Erlang program!
Listing 4: Running a compiled Erlang program.
Note that the name of the module defined in the file must be the same as the name of the file it is defined in.
A program can also be compiled from inside the Erlang interpreter. To do this the c()
function is used from within the shell. The previous program could for example be compiled like this:
Eshell V6.2 (abort with ^G)
1> c(first_program).
{ok,first_program}
Listing 5: Compiling a program from within the shell.
To invoke a compiled function just call it supplying both the module name and function name separated by a colon.
2> first_program:first().
Then first Erlang program!
ok
Listing 6: Invoking the compiling a program from within the shell.
Just as in Prolog, each statement and expression must be terminated by a single dot.
Comments in Erlang are created using %. % This is a comment.
Just like in Prolog, an atom is a constant where the value of the constant is equal to its name. Atoms always start with a lower case letter.
Variables in Erlang always have names starting with an uppercase letter.
6> AVar = 42.
...
42
Listing 7: Defining a variable
The variables in Erlang are single assignment variables. What does that mean? It means that once you have assigned a value to a variable, you cannot change its value. If you try to assign another value to the variable, Erlang will refuse to do so and print an error message. Variables for which no assignment have been made are called unbound variables while variables having values assigned to them are called bound variables.
Note that you can assign the same value to a variable over and over again. You just can't assign a different value to the variable after its original initialization.
Lists are defined using by enclosing a number of elements in brackets. The elements should, as usual, be separated by commas. E.g. [1,2,3]
.
An interesting fact is that a string is in fact a list:
2> [76, 65, 82, 83].
"LARS"
Listing 8: A string is in reality a list.
The elements of a list can be of different types and can be as many as will fit into the memory of the computer.
A list can be split into a head an a tail just like in Prolog.
1> AList = [1, 2, 3].
[1,2,3]
2> [Head|Tail] = AList.
[1,2,3]
3> Head.
1
4> Tail.
[2,3]
Listing 9: Splitting a list into head and tail.
The [Head|Tail]
construct can also be used as the right hand side of a statement. If used so it constructs a new list having Head
as its first element and the elements of the list Tail
as, well, the tail.
3> L = [1 | [2,3]].
[1,2,3]
4> L.
[1,2,3]
Listing 10: List construction using the [Head|Tail] construct.
Tuples are fixed size lists, as in many other program languages. They are defining by enclosing the elements in curly brackets:
4> {1,2,3,4}.
{1,2,3,4}
Listing 11: A tuple.
Tuples can be used to implement functionality that is provided by hash tables in other languages. We could for example create a data structure representing a person like this:
{person, {name, "Lars"}, {age, 42}}.
Listing 12: Tuples used to implement hash table like data structure.
In the example above I've used the atoms person
, name
and age
to as the keys in the "hash map". The person
atom seems redundant, but is useful if the program contains more than one hash table like this. Why is that? It has to do with the way data is extracted from a tuple, which is what we will discuss next.
Note that we can mix tuples and lists. We could for example let the tuple above contain a list of persons (then it probably would be a good idea to call the tuple persons
).
Pattern matching works similar in Erlang to how it works in Prolog. An atom will match itself. If any variables is present in a match expression, Erlang will examine the data structure matched against and try to assign values to the variables. Note that only a single solution, if any, is returned. Not all possible solutions as in Prolog. Let's look at an example.
We can perform pattern matching against the tuple in the previous section by assigning it to a variable and then using that in a match expression.
Eshell V6.2 (abort with ^G)
1> Lars = {person, {name, "Lars"}, {age, 42}}.
{person,{name,"Lars"},{age,42}}
2> {person, {name, LarsName}, {age, LarsAge}} = Lars.
{person,{name,"Lars"},{age,42}}
3> LarsName.
"Lars"
4> LarsAge.
42
Listing 13: Using pattern matching to retrieve values from a tuple.
In the example above, we see how the Erlang interpreter checks if both sides of the equals sign can be made equal, which they can, and by that process assigns values to any variables in the match expression.
If the expression did not match the data structure, an error message will be printed and no variable assignment would be made:
5> {person, {name, KalleName}, {shoeSize, KalleShoeSize}} = Lars.
** exception error: no match of right hand side value
{person,{name,"Lars"},{age,42}}
6> KalleName.
* 1: variable 'KalleName' is unbound
Listing 14: Variables not assigned when a matching fails.
A function belong to a module. A module is defined in a file with the extension .erl
. In the beginning of the file, the name of the module and the functions the module exposes are defined. Let's look again at the example we saw a few sections ago
-module(first_program).
-export([first/0]).
first() ->
io:fwrite("Then first Erlang program!")
Listing 15: Example of a module containing one function.
Here we define a module called first_program
. It exports one function, called first
. The /0
after the function name indicates that the function does not take any parameters. Had it takes one param, the export statement would have been first/1
instead.
If a module exports more than one function, several export
statements are used.
The body of the function comes after the name of the function and the mandatory ->
in the function definition. Several statements can be included in the body by separating them with commas. The value of the last expression in the body is returned as the value of the function call.
Several variants of a function can be defined, each matching a specific input. The different variant is separated by a semicolon. The last variant should be ended by a dot. To exemplify this we write a variant of the factorial program where we in addition to calculating the value we also print the number we which currently calculating the factorial for.
-module(factorial).
-export([fact/1]).
-export([print_number/1]).
fact(0) -> 1;
fact(N) ->
print_number(N), N * fact(N - 1).
print_number(N) ->
io:fwrite(integer_to_list(N)), io:fwrite("
").
Listing 16: Factorial program that print the current number.
In the program above, we define two functions: print_number
that just prints the number it receives as input on a separate line on std out and fact
that invokes print_number
in addition to calculating the factorial. Note that we separate the invocation of the print_number
and recursive call to fact
by a comma. This is how several statements are executed in sequence in a function: just separate them with commas.
Running the program to calculate 5! gives the following output:
3> factorial:fact(5).
5
4
3
2
1
120
Listing 17: Using the factorial program to calculate 5!.
It can be noted that Erlang executes recursive programs insanely quickly. Using the factorial program to calculate 1000! takes a tenth of a second or so (I haven't timed it, but it seems almost instantaneous).
An anonymous, no-name, function can be created using the fun
keyword. Such functions can be assigned to variables, passed to other functions and returned from other functions. Let's create such a function, assign it to a variable and using that variable to executing the function:
1> TimesTwo = fun(N) -> N * 2 end.
#Fun
2> TimesTwo(7).
14
Listing 18: Creating and invoking an anonymous function.
Case clauses use pattern matching to determine which of several paths to take. A case statement is started with case Variable of
. Then comes a list of cases where the matching value is separated from the resulting value by an arrow ->
. The different cases are separated from each other by a semicolon. Note that the last matching case should not have a trailing semicolon. The whole statement is then ended by end.
. You can add an underscore as a wildcard to catch all cases where no explicit match could be made.
An example:
1> SuiteAsString = "clubs".
"clubs"
2> case SuiteAsString of
2> "spades" -> spades;
2> "hearts" -> hearts;
2> "diamonds" -> diamonds;
2> "clubs" -> clubs;
2> "_" -> spades
2> end.
clubs
An if clause uses one or more guards to determine which path to select. A guard is just a boolean expression. Let's code up an example:
1> X = 42.
42
2> if
2> X < 40 ->
2> io:fwrite("Less than 40
");
2> X > 40 ->
2> io:fwrite("Greater than 40
")
2> end.
Greater than 40
ok
Listing 19: Basic if clause.
Note that every "part" of the if clause but the last is terminated by a semicolon.
If more than one statement should be executed in a branch, the statements should be separated by commas. Here's a small function that just demonstrates how to have several statements in each branch.
-module(if_test).
-export([iftest/1]).
iftest(Y) ->
X = Y + 2,
if
X < 40 ->
io:fwrite("Less than 40
"),
io:fwrite("Will terminate
");
X > 40 ->
io:fwrite("Greater than 40
"),
io:fwrite("Will not terminate
")
end.
...
18> c(if_test).
{ok,if_test}
19> if_test:iftest(37).
Less than 40
Will terminate
ok
20> if_test:iftest(39).
Greater than 40
Will not terminate
ok
Listing 20: If clause having several statements in each branch.
If an if clause does not cover all possible values, Erlang throws an exception:
21> if_test:iftest(38).
** exception error: no true branch found when evaluating an if expression
in function if_test:iftest/1 (if_test.erl, line 6)
Listing 21: An error occurs if executing an if clause with a non-covered value.
To provide a default, catch-all case, include a true
part in the if clause:
-module(if_test).
-export([iftest/1]).
iftest(Y) ->
X = Y + 2,
if
X < 40 ->
io:fwrite("Less than 40
"),
io:fwrite("Will terminate
");
X > 40 ->
io:fwrite("Greater than 40
"),
io:fwrite("Will not terminate
");
true ->
io:fwrite("Executing the default branch
")
end.
...
22> c(if_test).
{ok,if_test}
23> if_test:iftest(38).
Executing the default branch
ok
Listing 22: if clause with a true branch.
The foreach
function defined in the lists
module can be used to iterate over the elements of a list. An example of when this could be useful if you want to print the elements of the list.
1> Numbers = [1,2,3,4,5].
2> lists:foreach(fun(X) -> io:format("Element is: "), io:format("~p", [X]), io:format("
") end, Numbers).
Element is: 1
Element is: 2
Element is: 3
Element is: 4
Element is: 5
ok
Note that the elements of the list are not modified by foreach
.
map
is a function defined on lists. It can be used to apply a function to all members of the list. E.g. to apply the function that multiplies an input value by two that we defined in the section about anonymous functions above to a list of integers, the following code can be used:
3> Numbers = [1,2,3,4,5].
7> lists:map(TimesTwo, Numbers).
[2,4,6,8,10]
Listing 23: Using map to applying a function to all elements of a list.
Of course, we can define and use an anonymous function directly in the call to map
:
11> lists:map(fun(X) -> X * 3 end, Numbers).
[3,6,9,12,15]
Listing 24: Defining an anonymous function directly in the call to map.
The function lists:filter
is used for forming a new list from an old one, where the new list only contains a subset of the elements of the old list. The selection is made based the evaluation of a predicate: if the predicate evaluates to true for an element, the element will be included in the new list, if it evaluates to false, it will not.
As an example, we construct a small program that selects all elements greater than two from a list of integers:
1> L = [1,2,3,4,5].
[1,2,3,4,5]
2> GreaterThanTwo = fun(X) -> X > 2 end.
#Fun
3> lists:filter(GreaterThanTwo, L).
[3,4,5]
Listing 25: Using filter to selects elements greater than two.
lists:any
and lists:all
can be used to determine if any respectively all of the elements in a list fulfills a predicate.
We can use the predicate function and the list from the previous section to test the lists:any
and lists:all
functions:
5> lists:any(GreaterThanTwo, L).
true
6> lists:all(GreaterThanTwo, L).
false
Listing 26: Example of any() and all().
The functions lists:takewhile
and lists:dropwhile
can be used either extract (takewhile
) or discard (dropwhile
) elements from the beginning of a list.
9> LessThanThree = fun(X) -> X < 3 end.
#Fun
10> lists:takewhile(LessThanThree, L).
[1,2]
11> lists:dropwhile(LessThanThree, L).
[3,4,5]
Listing 27: Example of takewhile() and dropwhile().
foldl
can be used to accumulate some kind of result from a list. It can be a sum, a product or something else. To do this foldl
uses an accumulator function that is applied to each element in the list in turn. The accumulator function receives the currently accumulated value as inparam and uses that value and the value of the element to determine the new accumulated value. As no accumulated value exists from the beginning, foldl
takes a separate argument, which represents the value to start accumulating from. This would typically be 0 for a summing operation and 1 for a multiplying one.
The most basic example of foldl
is determining the sum of a list of integers. We use the same list as in the previous sections.
14> L.
[1,2,3,4,5]
15> lists:foldl(SumFunction, 0, L).
15
Listing 28: Using foldl() to sum the values in a list.
You can pack the values of a number of variables into a bitmap using the <<VariableName1:numberOfBitsForVariable1, VariableName2:numberOfBitsForVariable2,...>>
syntax. I.e. to pack three variables into a bit pattern of 12 bits the following code can be used:
1> A = 7.
7
2> B = 14.
14
3> C = 29.
29
4> BitMap = <<A:3, B:4, C:5>>.
<<253,13:4>>
Listing 29: Packing data into a bitmap.
To unpack data the reverse syntax is used:
5> <<APrim:3, BPrim:4, CPrim:5>> = BitMap.
<<253,13:4>>
6> APrim.
7
7> BPrim.
14
8> CPrim.
29
Listing 30: Unpacking data from a bitmap.
Basically a list comprehension creates a new list from one or more source lists. What's special with list comprehension is that using a compact syntax, the elements used from the source lists can be filtered and/or transformed before being used to form the new list.
As a simple first example, lets use a list comprehension to create a new list from a source list where one is added to each element in the source list. Here no filtering of the source list is performed and only one simple transformation (the addition of 1 to the source elements).
1> [X + 1 || X <- [1,2,3,4]].
[2,3,4,5]
Listing 31: Simple example of a list comprehension.
The stuff on the right hand side of ||
decides what source elements to include. The stuff on the left of ||
determines what transformations to apply to those elements before including them in the resulting list.
Another way to explain it is that the right hand side sets up variables (in this example only on variable X
is set up) that can be used in the left hand side. The variables are assigned a number of values (here 1,2,3 and 4). The left hand side performs transformations the values specified by the variables and the results of those transformations become the elements of the new list.
Several variables can be set up on the left side. If that is done, all permutations of the values given to the variables will be processed by the list comprehension. For example to create a list of 2-tuples where the first element of the tuples is 1 or 2, and the second element is 100 or 200, the following code can be used:
2> [{X, Y} || X <- [1,2], Y <- [100, 200]].
[{1,100},{1,200},{2,100},{2,200}]
Listing 32: List comprehension using two generators.
Each such expression Variable <- SomeList
is called a generator.
Each generator can have one or more filter that filters what elements from the source list that will be passed to the left hand side. We can rewrite our first example of a list comprehension so that only elements greater than one will be passed to the left hand side (which will add 1 to the values it receives):
3> [X + 1 || X <- [1,2,3,4], X > 1].
[3,4,5]
Listing 33: List comprehension with a filter included.
Each generator can have several filters. To exemplify this, we add a second filter to our example so that integers in the [2,3] interval is used a source values:
7> [X + 1 || X <- [1,2,3,4], X > 1, X < 4].
[3,4]
Listing 34: List comprehension with one generator having two filters.
The main strength of Erlang lies in its concurrency support. It is possible to write really, really robust concurrent programs in it. The basic idea is that a program starts one or more processes which encapsulates the things that should be processed concurrently, and one (or more) monitor process(es) that monitors the worker processes. If one of the worker processes crashes, the monitor process will be informed and can then start a new worker process to fill in for the one that crashed. The monitor processses can in turn be monitored by a set of "meta-monitor processes" and so on. This way of writing concurrent software, it done correctly, will lead to really robust programs as we don't have to anticipate the different kinds of errors that might occur. We just let the worker process crash and start a new one when this happens. The Erlang "slogan" of this was of writing programs is Let it crash!.
A note about terminology: in Erlang a process is a small self-contained virtual machine that can execute Erlang functions.
So how do you write such a program in Erlang? It turns out that you only need to know a few additional keywords:
receive
: Receive a message from another (or its own) process.
!
: Send a message to another (or its own) process.
spawn
: Create a new process.
link
: Link the current process with another process in order to monitor that process.
spawn_link
: Spawn a new process and link this process to that process in order to monitor it.
register
: Associate a specific process with a symbol. The symbol can then be used to send messages to the process.
For correctness sake, it should be said that !
and receive
exchange messages via a mailbox. I.e. !
sends a message to a mailbox, from which receives
then fetches it.
As an example, let's write a small program that can be started to execute in a separate process and that listens to messages from other processes:
-module(worker_service).
-export([start/0, rpc/1]).
start() ->
register(workerService, spawn(fun loop/0)).
rpc(RequestParameter) ->
workerService ! {self(), RequestParameter},
receive
{workerService, Response} ->
Response
end.
loop() ->
receive
{From, Request} ->
io:format("Received request: ~p~n", [Request]),
From ! {workerService, "Thank you for your message!"},
loop()
end.
Listing 35: Message listener program.
We have included a function start()
that spawns a process and runs the loop()
function in it. It also registers the name 'workerService' with the spawned process, Another function that we've included is rpc()
which sends a message to the process spawned by start()
.
The main function of the module is loop()
. It is the method that receives and responds to messages. Our implementation of the message listener is extremely simple: we just log that we've received a message, send a return message containing a nice greeting back to the sender and go back to listening for new messages.
Now let's write function that supervises the workerService
process and restarts it if it should crash:
-module(worker_supervisor).
-export([start/0]).
start() ->
register(workerSupervisor, spawn(fun loop/0)).
loop() ->
process_flag(trap_exit, true),
link(whereis(workerService)),
receive
{'EXIT', From, Reason} ->
io:format("Worker service died with reason ~p~n", [Reason]),
worker_service:start(),
loop()
end.
Listing 36: Program that supervises the worker service.
In this program we've included a function start()
, that analogously to the start()
function in the worker_service
progra, spawns a new process in which it listens to incoming messages. It also registers a name for the newly spawned process so that we can easily access it should we want.
The loop()
function is where the interesting stuff happens. First we tell the Erlang runtime system that we want to receive an exit signal when one of the processes we are linked to exists. Then we set up a link to the worker_service
process that the previous program spawns off. After that we starts to listen to messages. More specifically we listens to EXIT messages. Such a message will be sent by the runtime system when the when the workerServie
process exists. When having received such a message, the listener will print a message, start up a new worker process and rerun the loop()
method again. Pretty nifty eh?
Example output:
1> c(worker_supervisor).
worker_supervisor.erl:11: Warning: variable 'From' is unused
{ok,worker_supervisor}
2> worker_service:start().
true
3> worker_supervisor:start().
true
4> worker_service:rpc("Hello!").
Received request: "Hello!"
"Thank you for your message!"
5> exit(whereis(workerService), kill).
Worker service died with reason killed
true
6> worker_service:rpc("Are you alive?").
Received request: "Are you alive?"
"Thank you for your message!"
Listing 37: Sample output from running the worker and supervisor programs.
We've used the built in exit()
and whereis()
functions to find and kill the worker process, and we can see that when we did just that, the supervisor received the exit signal and took apropriate action. I.e. started a new worker process.
- Forgetting that the last part of a case statement or an if statement should not end with a semicolon.
-module(fizzBuzz).
-export([start/0]).
start() -> f(1).
f(101) -> void;
f(N) ->
if
N rem 3 =:= 0 andalso N rem 5 =:= 0 ->
io:format("FizzBuzz");
N rem 3 =:= 0 ->
io:format("Fizz");
N rem 5 =:= 0 ->
io:format("Buzz");
true -> io:format("~B", [N])
end,
io:format("~n"),
f(N +1).
- It executes (tail) recursive programs extremely quickly.
- Its concurrency support. It is quite easy to set up a process that monitors another process and restarts it if it crashes.
- List comprehensions. Almost like in Python ;)
- It's hard (at least for a beginner) to remember what terminating character, if any, to end each line with.
I must say that I like Erlang. I'm not sure why. It sometimes got a pretty strange syntax, probably because its Prolog inheritance, but on the whole, it is a really nice programming language. And its concurrency support is...marvelous :)
Now its time for another exciting language: Clojure!
1. Ruby
2. Io
3. Prolog
4. Scala
[1] Erlang homepage
2015-11-30
Clojure is a LISP dialect with some extensions and some simplification. Like Scala and a small army of other languages, Clojure runs on the JVM with all the benefits that comes with doing so. It is a functional language that, as Erlang, contains some non-functional things.
Whenever i think of LISP, I remember the old joke that LISP stands for Lots of Irritating Stupid Parenthesis. There are lots of parenthesis in Clojure, but not so many as in regular LISP. As we will see, lists, sets and hashes uses other start and end markers. Commas are even allowed in some places. That's what I call progress ;)
Clojure is strongly and dynamically typed.
Clojure programs can be executed in an interpreter called repl. To start repl one can either download the Clojure distribution from clojure.org, unpack it and run java -cp clojure-1.7.0.jar clojure.main
or one can use leinigen. Leiningen is i project for packaging and running Clojure programs. After having installed it one can invoke lein repl
to start the interpreter.
Clojure programs can also be executed directly from the command line by storing it a text file (conventionally having the .clj extension) and invoking (here assuming that the installed version is 1.7.0) java -cp ~/opt/clojure/clojure-1.7.0/clojure-1.7.0.jar clojure.main myProgram.clj
Once the interpreter is started, what can be done with it?
In Clojure, like in all functional languages, mostly everything is about function. To invoke a function in Clojure, one encloses the name of the function and parameters in parenthesis. The name goes right after the opening parenthesis, then comes the params separated by spaces. An example:
user=> (functionOne paramOne paramTwo)
Listing 1: Invoking a function
Invoking an operator is done in the same way, using prefix notation:
user=> (/ 12 3)
...
4
Listing 2: Using the division operator.
In the example we invoke the division operator supplying 12 as the nominator and 3 the denominator.
It is also possible to supply more than two operands to an operator. The operator is applied to the operands from left to right.
user=> (/ 12 3 2)
2
...
Listing 3: Using more than two operands.
Operations can be nested as desired:
user=> (/ (+ 8 4) (* 1 3) (- 4 2))
...
2
Listing 4: Nesting operations.
Clojure uses semicolons for line comment and the construct (comment)
for block comments. An example:
user=> (+ 37 5) ; The answer to everything.
42
Listing 5: Line comment.
Block comments can be used like this:
user=> (comment This is a longer comment
#_=> split
#_=> over
#_=> several lines.)
...
nil
Listing 6: Block comment.
Text can be written to std out by invoking the println
function. Thus, the Hello World program can be written like this:
user=> (println "Hello World!")
...
Hello World!
nil
Listing 7: Hello World in Clojure
Other types can be converted to strings using the str
function. If the object being converted is an instance of a Java class, the underlying objects toString()
method will be invoked.
str
can take from one to many arguments and the result will be be concatenated string that consists of the string representations of all the parameters. Using str
is therefore an easy way to concatenate a number of strings and other types together. An example:
user=> (str "one " 1 " two " 2)
"one 1 two 2"
Listing 8: Using str to concatenate things together.
A variable can be assigned using the (def variableName value)
construct. E.g.
(def message "Hello!")
...
user=> (def message "Hello!")
#'user/message
Listing 9: Defining a variable
The value of the variable can be found out by just stating the name of the variable at the prompt:
user=> message
"Hello!"
Listing 10: Printing the value of a variable.
Clojure has the regular comparison operators: <, > <=, >= and =
. The "not equals" operator looks like this: not=
.
Just like symbols in Ruby and atoms in Erlang, Clojure has a concept of "an immutable thingy representing something the programmer wants it to represent". I Clojure such a thing is called a keyword and is constructed by prepending a colon to a name: :thisIsAKeyword
A list can be constructed in two different ways. The first is to use the list
function:
user=> (list "a" "b" "c")
("a" "b" "c")
Listing 11: Creating a list using the list function.
The other way to construct a list is by using quoting:
user=> '("d" "e" "f")
...
("d" "e" "f")
Listing 12: List constructed via quoting.
So why can't a list be constructed like this ("This" "does" "not" "work")
? That's because lists are evaluated as functions. The list in this example would be interpreted as a call to the method named "This" with params "does", "not" and "work". And that does not work.
The basic operations that are possible to perform on a list are these:
first
- extract the first element of the list.
rest
- extract sublist consisting of everything but the first element.
last
- extract the last element of the list.
cons
- construct a new list by appending a head to a tail.
An example exercising the basic functions:
user=> (first '(1 2 3 4 5 6))
1
user=> (last '(1 2 3 4 5 6))
6
user=> (rest '(1 2 3 4 5 6))
(2 3 4 5 6)
user=> (cons 0 '(1 2 3 4 5 6))
(0 1 2 3 4 5 6)
Listing 13: Basic list functions.
A vector is similar to a list. One difference between them is that vectors are optimized for random access, lists are not. Another difference is more conceptual: lists are meant to be used for code, vectors for data.
To create a vector the bracket syntax is used: ["a" "b" "c" "d"]
user=> ["a" "b" "c" "d"]
...
["a" "b" "c" "d"]
Listing 14: Creating a vector.
Some of the operations that can be performed on vectors are these:
first
- extract the first element of the vector.
rest
- extract a vector consisting of everything but the first element.
last
- extract the last element of the .
nth
- return the n:th element of the vector.
user=> (["a" "b" "c"] 0)
"a"
user=> (first ["a" "b" "c"])
"a"
user=> (rest ["a" "b" "c"])
("b" "c")
user=> (last ["a" "b" "c"])
"c"
user=> (nth ["a" "b" "c"] 1)
"b"
Listing 15: Basic vector functions.
Vectors can also be used as functions themselves taking an index as a parameter. When a vector is used as a function it returns the element having the supplied index.
user=> (["a" "b" "c"] 0)
...
"a"
Listing 16: Vector used as a function.
A set is created similar to how a list or a vector is created but instead of using parenthesis or brackets, the elements are enclosed by #{}
.
user=> #{"a" "b" "c"}
#{"a" "b" "c"}
Listing 17: Creating a set.
As a set does not have any order, it is of course not possible to fetch individual elements of a set but several other operations exist on sets. The following lists some of them. In the examples the following set are used
(def aSet #{"H" "A" "L"})
count
- count the number of elements in a set :(count aSet)
3
sort
- creates a list in which the elements in the set are sorted: user=> (sort aSet)
("A" "H" "L")
sorted-set
- returns a sorted set containing the parameters given to it: user=> (sorted-set "H" "A" "L")
#{"A" "H" "L"}
clojure.set/union
- returns a set that is the union between two sets. user=> (clojure.set/union #{1 2 3} #{3 4 5})
#{1 4 3 2 5}
clojure.set/difference
- returns a set that is the difference between two sets: user=> (clojure.set/difference #{1 2 3} #{3 4 5})
#{1 2}
To test if an element is contained in a set, a somewhat peculiar syntax can be used. One simply uses the set as a function a gives the value that should be tested for membership as a param. If is is included, the value itself will be returned. If not then nil
will be returned:
user=> (#{1 2 3} 2)
2
user=> (#{1 2 3} 42)
nil
Listing 18: Testing is a value is included in a set.
Maps can be created by enclosing a comma separated list of items in curly brackets. Each item should consist of a key and a value separated by a space.
{"key1" "value1", "key2" "value2"}
To retrieve the value for a specific key, just invoke the map as you would have a function and pass the key as argument:
user=> (def aMap {"key1" "value1", "key2" "value2"})
#'user/aMap
user=> (aMap "key1")
"value1"
Listing 19: Retrieving a value for a key from a map.
Interesting enough, if the keys in the map are keywords, a keyword can be used like a function to look up its corresponding value in a map:
user=> (def bMap {:keyA "valueA", :keyB "valueB"})
#'user/bMap
user=> (:keyB bMap)
"valueB"
Listing 20: Using keywords in a map.
So what operations can be performed on a map? Here are some:
-
merge
- merge two maps: user=> (merge {"one" 1, "two" 2} {"three" 3, "four" 4})
{"one" 1, "two" 2, "three" 3, "four" 4}
-
merge-with
- merge two maps specifying an operator to use when keys exist in both maps: user=> (merge-with + {"one" 1, "two" 2} {"two" 2, "three" 3})
{"one" 1, "two" 4, "three" 3}
-
assoc
- add a key-value pair to a map: user=> (assoc {"one" 1} "two" 2)
{"one" 1, "two" 2}
-
sorted-map
- creates a map in which the keys are sorted from a number of key-value pairs: user=> (sorted-map 2 "two", 1 "one", 3 "three")
{1 "one", 2 "two", 3 "three"}
The defn
keyword is used to defined a function:
(defn aFunction [anArgument] (str "The argument was: " anArgument))
user=> (aFunction "this is the argument")
"The argument was: this is the argument"
Documentation can be added to a function by supplying an extra string between the name of the function and the parameter list:
user=> (defn greet "Prints a nice greeting" [] (str "Hello dear sir/madam! What a lovely day!"))
. Info about a function can be retrieved by invoking the doc
function: user=> (doc greet)
-------------------------
user/greet
([])
Prints a nice greeting
nil
Destructuring is the concept of picking out parts of a parameter list containing list, vectors, maps and similar data structure. It's somewhat similar to pattern matching i Prolog. So how does destructuring look? One simply type a data structure like the one that the one the function expects in the parameter list, names the parts that should be extracted and puts under scores in the places where the parts that could be ignored are. For example, of a function expects a vector of which only the second element if of interest, the pattern [_ second _]
could be used inside the parameter list. Let's try it out:
user=> (defn aFunction [[_ second _]] (str "The second element of the vector was " second))
#'user/aFunction
user=> (aFunction [1 2 3 4])
"The second element of the vector was 2"
Listing 21: Destructuring a vector.
The data structure that is destructured could be nested in one or more levels. E.g. the second element of the second element in a vector containing other vectors could be extracted like this:
user=> (defn bFunction [[_ [_ secondSecond _] _]] (str "The second element of second element of the input the vector was " secondSecond))
#'user/bFunction
user=> (bFunction [[1 2 3] [4 5 6] [7 8 9]])
"The second element of second element of the input the vector was 5"
Listing 22: Destructuring a vector of vectors.
To shorten the code a bit, trailing under scores could be skipped. The function above could thus be constructed like this:
user=> (defn bFunction [[_ [_ secondSecond]]] (str "The second element of second element of the input the vector was " secondSecond))
#'user/bFunction
user=> (bFunction [[1 2 3] [4 5 6] [7 8 9]])
"The second element of second element of the input the vector was 5"
Listing 23: Destructuring a vector of vectors with trailing under scores removed.
In addition to parameter lists, destructuring can also be performed using a let
statement. It take a pattern and a value to apply that pattern to as first param. As second param it takes an expression or a statement that, presumably, uses the data that has been extracted during the destructuring. We could rewrite the previous example extracting the second element of the second vector like this:
user=> (defn middle [matrix]
#_=> (let [ [_ [_ mid _] _] matrix] (str "The second element of second element of the input the vector was " mid)))
#'user/middle
user=> (middle [[1 2 3] [4 5 6] [7 8 9]])
"The second element of second element of the input the vector was 5"
Listing 24: Destructuring a vector of vectors using let.
If clauses in Clojure is similar to those in Io.
user=> (if true (println "It is true!"))
It is true!
nil
Listing 25: Simple if clause
To test several conditions and
and or
can be used.
user=> (if (and true true) (println "This is true"))
This is true
nil
user=> (if (and true false) (println "This is false"))
nil
user=> (if (or true false) (println "This is true"))
This is true
nil
Listing 26: and and or
Clauses including an else part can be constructed like this:
user=> (if (< 2 1) (println "Two is less than one") (println "Two is greater than one"))
...
Two is greater than one
nil
Listing 27: If clause containing an else part.
To be able to evaluate several expressions in one (or both) parts of an if expression, the do
expression can be used. It evaluates all expressions given as parameters to it and returns the value of the last. An example:
user=> (if true (do (println "This is printed") (println "This is also printed")))
This is printed
This is also printed
nil
Listing 28: If clause containing a do expression.
This can also be accomplished using the when
expression. It is like an if
with an implicit do
:
user=> (when true (println "This is printed") (println "This is also printed"))
This is printed
This is also printed
nil
Listing 29: when expression.
Like in many (most?) other functional languages a function can be applied to all elements in a list/vector by using map
. An example of doing just that can look like this:
ser=>(def aList '("a" "bb" "ccc" "dddd"))
#'user/aList
user=> (map count aList)
(1 2 3 4)
Listing 30: Using map to count the number of letters of each item in a list.
Of course it is not only built in functions like count
that can be applied to a list with map
. User defined functions works equally well. If we for example define a function that adds one to the integer it receives as input, we can apply that to a list of integers like this:
user=> (defn plusOne [i] i + 1)
#'user/plusOne
user=> (def bList '(1 2 3 4 5))
#'user/bList
user=> (map plusOne bList)
(2 3 4 5 6)
Listing 31: Applying a custom function to a list of integers using map.
filter
can be used to, well, filter elements in a list. It takes a condition and a list, and returns a new list containing only the elements of the new list that fulfills the condition. An example:
user=> (def bList '(1 2 3 4))
#'user/bList
user=> (defn lessThanThree [i] (< i 3))
#'user/lessThanThree
user=> (filter lessThanThree bList)
(1 2)
Listing 32: Using filter to extract elements from a list.
To apply a function to a list as a whole apply
can be used. E.g. to sum the elements in a list, the following code can be used:
user=> (apply + bList)
10
Listing 33: Using apply to sum the elements in a list.
Creating a named function just to be able to use it in a map
call can be a little burdensome. Writing an anonymous function is a way to create a function with a little less overhead. It is meant to be used when the function does not need to have a name. For example, when it just should be used as a parameter to a map
call.
An anonymous function is creating using the (fn [params] (body))
syntax. If we rewrite our earlier example of using map
to increase the integers in a list by 1 using an anonymous function, we get this:
user=> (map (fn [i] (+ i 1)) bList)
(2 3 4 5)
Listing 34: Using an anonymous function to increase the integers in a list by one.
There is also a shorter form of an anonymous function called a reader macro. When using that, the fn
is replaced by a #, no argument list is used but the single parameter that is supplied can be accessed by the % symbol. The previous example could thus be rewritten like this:
user=> (map #(+ % 1) bList)
(2 3 4 5)
Listing 35: Using a reader macro to increase the integers in a list by one.
To condense the elements in a list to a single value reduce
can be used. E.g. to sum the elements in a list of integers, you could do like this:
user=> (def aList '(1 2 3 4 5))
#'user/aList
user=> (reduce + aList)
15
A list can be sorted using sort
:
user=> (def bList '(1 2 99 4 5 -1 73 58))
#'user/bList
user=> (sort bList)
(-1 1 2 4 5 58 73 99)
A function can be used to supply the sort order. In this case sort-by
is used instead of sort
.
user=> (defn abs [i] (if (< i 0) (* i -1) i))
#'user/abs
user=> (def cList '(0 -2 103 -4 5 -1 -73 58))
#'user/cList
user=> (sort-by abs cList)
(0 -1 -2 -4 5 58 -73 103)
Listing 36: Using a function to supplying the sort order.
As in other functional languages, recursion is used heavily in Clojure. A function computing the factorial of a number can for example be implemented like this:
(defn factorial [n]
(if (= n 0)
1
(* n (factorial (- n 1)))
)
)
Listing 37: Recursive factorial program.
Quite surprisingly considering it is a functional language, Clojure supports an explicit iteration construct using the loop
and recur
keywords. The loop
sets up the variables used in the loop. recur
calls the corresponding loop
statement passing on new values for the parameters to be used in the next iteration. To avoid looping indefinitely the body of the loop must of course contain an if
clause in which the recur
statement is put.
A function using loop-recur
to calculate the factorial could look like this:
(defn factorialUsingLoop [n]
(loop [nn n, product 1]
(if (<= nn 1)
product
(recur (- nn 1) (* product nn))
)
)
)
Listing 38: Factorial program using loop-recur.
So how does the program above work? It takes one param which, obviously, is the number to calculate the factorial of. In the loop
construct, we set up two variables nn
and product
. nn
is the counter which we will use to keep track of which iteration we are currently on and product
is an accumulator in which we store the current result. The variables are initially assigned the values n and 1 respectively.
Next comes the guard statement: if we've reached 1 we're finished and can return the current product as the final result. Otherwise the use recur
to jump back to the loop
statement. This time however, we pass new values to loop
by supplying parameters to recur
. The counter is decreased by one to indicate that we've completed an iteration and we multiply the accumulator by the current value of nn. The latter is of course how we calculate the factorial. In the next iteration of the loop these values will be assigned to nn
and product
respectively.
The class of a variable can be determined by using the class
function:
user=> (class 1)
...
java.lang.Long
Listing 39: Using class
Static Java methods can be called using the ClassName/methodName
syntax:
user=> (System/currentTimeMillis)
1448913149737
Listing 40: Calling a static Java method without params.
Params are passed into the method as usual:
user=> (System/getProperty "java.vm.version")
"25.45-b02"
Listing 41: Calling a static Java method and passing params to it.
Static fields are accessed in the same way minus the parenthesis:
user=> Integer/MAX_VALUE
2147483647
Listing 42: Accessing a static field.
Instance methods use almost the same syntax but a period is added before the name of the method:
user=> (.toLowerCase "tHE cAPS lOCK kEY mUST dIE!")
"the caps lock key must die!"
Listing 43: Accessing an instance method
Instance fields are accessed by prepending .-
to the instance:
user=> (.-x (new java.awt.Point 10 200))
10
Listing 44: Accessing an instance field
In the last example above we also saw how to create a new Java object, using the new
function. As an alternative syntax, we can skip new
and add a period right after the name of the class:
user=> (.-x (java.awt.Point. 10 200))
10
Listing 45: Creating an instance of a class using the dot notation instead of using new.
All containers in Clojure are members of a concept that's called a sequence. There are a lot of functions in Clojure that operates on sequences. Let's explore some of them.
First out are some predicate functions. These are functions that take a test function as param and returns a boolean value. The return value is calculated by applying the test function to a number of the elements of the sequence. How many elements that need to be considered depends on the predicate function. The every?
function needs to process all elements, the not-every?
function can stop as soon as a non-compliant element is found.
Here's some of the predicate functions that exist in Clojure
every?
- does every element in the secuquence make the test function return true.
not-every?
- is there at least one element in the secuquence that makes the test function return false.
some
- is there at least one element in the secuquence that makes the test function return true.
not-any?
- is there no element in the secuquence that makes the test function return true
Note that some
does not end with a question mark!
As an example, let's write a test function that checks if a number is equal to 42 and use that with the predicate functions.
(defn isFortytwo? [i] (= i 42))
Listing 46: The extremely important function that checks if a number is equal to 42.
We can then use that function together with the predicate functions above by applying them to a test sequence (a list in this case):
user=> (def aSequence '(1 2 3 42 4711))
#'user/aSequence
user=> (every? isFortytwo? aSequence)
false
user=> (not-every? isFortytwo? aSequence)
true
user=> (some isFortytwo? aSequence)
true
user=> (not-any? isFortytwo? aSequence)
false
Listing 47: Using predicate functions to check for the occurrence of 42 in a list.
Just like Erlang (and Python), Clojure supports list comprehensions. The syntax is like this: (for [aVariable aList, bVariable, ...] (doSomethingWithTheVariables))
. For example, if you want to loop through a list of chocolate bars and a list of sizes, you can do that with a single list comprehension like this:
user=> (def items '("Twix bar" "Snickers bar" "Milka bar"))
#'user/items
user=> (def sizes '("small" "medium" "large"))
#'user/sizes
user=> (for [item items, size sizes] (str "Item " item " of size " size))
("Item Twix bar of size small" "Item Twix bar of size medium" "Item Twix bar of size large" "Item Snickers bar of size small" "Item Snickers bar of size medium" "Item Snickers bar of size large" "Item Milka bar of size small" "Item Milka bar of size medium" "Item Milka bar of size large")
Listing 48: Using a list comprehension to generate all permutations of mixing two lists.
A filter can be applied to the list comprehension by applying the :when
keyword to the list comprehension
user=> (defn notSmall [size] (not= size "small"))
#'user/notSmall
user=> (for [item items, size sizes :when (notSmall size)] (str "Item " item " of size " size))
("Item Twix bar of size medium" "Item Twix bar of size large" "Item Snickers bar of size medium" "Item Snickers bar of size large" "Item Milka bar of size medium" "Item Milka bar of size large")
Listing 49: List comprehension including a filter.
A bounded sequence
can be created with the range
key word. For example to create a list of the numbers from one to ten you can do like this: user=> (range 1 10)
(1 2 3 4 5 6 7 8 9)
Listing 50: Using a range to create a bounded sequence.
A step could be added to the generation by supplying an extra parameter to range
. To generate all odd numbers in the interval from one to ten, this code could be used:
user=> (range 1 10 2)
(1 3 5 7 9)
Listing 51: Adding a step to range
.
Clojure has a lot of ways to generate indefinite sequences. It uses lazy evaluation to just pick the data that is needed at the moment.
To avoid the problem of printing an indefinite sequence, if that would be possible, we will in the examples below use the take
function that extracts the first n elements from a sequence and skips the others.
So what ways are there then to generate an indefinite sequence? Here are some:
-
repeat
- just repeats the value passed to it as an argument: user=> (take 3 (repeat "hej"))
("hej" "hej" "hej")
-
cycle
- cycles through the elements of a vector: user=> (take 5 (cycle ["the" "lazy" "fox"]))
("the" "lazy" "fox" "the" "lazy")
-
iterate
- repeatedly applies a function to a value. The initial value is passed to the first iteration. Then result of each iteration is passes on to the next iteration : #'user/timesThree
user=> (take 5 (iterate timesThree 1))
(1 3 9 27 81)
If you want you can pick out an element at a specific index from a sequence by using nth
:
user=> (nth (iterate timesThree 1) 5)
243
Listing 52: Picking out an element at a specific index.
Elements can be dropped from an indefinite sequence by applying drop
:
user=> (take 5 (drop 3 (iterate timesThree 1)))
(27 81 243 729 2187)
Listing 53: Dropping elements from an indefinite sequence.
If we want to place some fixed element between the elements in the indefinite sequence, this can be accomplished by using interpose
:
user=> (take 5 (interpose 42 (iterate timesThree 1)))
(1 42 3 42 9)
Listing 54: Placing a fixed element between each element of an indefinite sequence.
Two indefinite sequence can be interleaved by using, wait for it, interleave
:
user=> (take 9 (interleave (iterate timesThree 1) (cycle '("the" "lazy" "fox"))))
(1 "the" 3 "lazy" 9 "fox" 27 "the" 81)
Listing 55: Interleaving two indefinite sequences.
If we would like to change the syntax and have the sequence to the left and the operators that operate on the sequence to the right, we can do that by using the ->>
operator. I.e we start with the ->>
operator, then the generator for the sequence and finally we add one or more operators to the right. For example if we would like to extract the fourth to eighth of the sequence 1, 3, 9, 27, ..., we could do like this:
user=> (->> (iterate timesThree 1) (drop 3) (take 5))
(27 81 243 729 2187)
Listing 56: Using ->> to reverse the order in which operations of indefinite sequences are written.
Macros is a way to extend the Clojure language itself. So why can't we do this by just creating functions and invoking them? The basic problem is that all parameters in a function call always get evaluated at the time the function is called. That might not be what we desire. If, for example, we want to create a function printMessageIfFirstParamIs42
that takes a number and a code block as parameter. The code block should only be executed if the number is 42. This is impossible to accomplish using a function because the code block will be executed when the call to the function is made in the process of expanding the parameters.
user=> (defn printMessageIfFirstParamIs42 [i codeBlock] (if (= i 42) codeBlock))
#'user/printMessageIfFirstParamIs42
user=> (printMessageIfFirstParamIs42 4711 (println "It was 42!"))
It was 42!
nil
Listing 57: Non-working function.
The problem here, as stated before, was that the code block (println "It was 42!")
was evaluated before printMessageIfFirstParamIs42
.
So how do we solve this? Macros to the rescue! ;)
A macro let you defer the execution of a piece of code until "later". First the macro is expanded, then the expanded code is executed.
A macro is defined using defmacro
and all code that should be included in a macro should be packed in lists. Also all keywords (if
, not
, ...) needs to be prepended by a quote to avoid confusing the interpreter.
We can rewrite our printMessageIfFirstParamIs42 function like as a macro:
user=> (defmacro printMessageIfFirstParamIs42 [i codeBlock] (list 'if (= i 42) codeBlock) )
#'user/printMessageIfFirstParamIs42
user=> (printMessageIfFirstParamIs42 4711 (println "It was 42!"))
nil
user=> (printMessageIfFirstParamIs42 42 (println "It was 42!"))
It was 42!
nil
Listing 58: A working version of printMessageIfFirstParamIs42 written as a macro.
Clojore has constructs to support Software Transactional Memory (STM). So what does that mean? It means that we can create variables whose values only can be altered from a transaction. What benefit has that then? If one want to share state between two or more concurrently executing threads, STM helps making sure that only one thread at a time alters the value of the variable.
One language construct that supports STM in Clojure is the reference or ref for short. A ref is created using the ref
. For example, to create a ref that should hold the current language under examination, you can do like this: user=> (ref "Clojure")
#object[clojure.lang.Ref 0x1e11eb69 {:status :ready, :val "Clojure"}]
Listing 59: Creating a reference.
Note that we've only created a reference, we've not assigned a value to it (so it is pretty useless ;). To do that we create a regular variable and associate it with the ref
user=> (def languageUnderInvestigation (ref "Clojure"))
#'user/languageUnderInvestigation
user=> @languageUnderInvestigation
"Clojure"
Listing 60: Creating a reference and assigning it to a variable.
To retrieve the value of a reference you use deref
or its shorter form @
:
user=> (deref languageUnderInvestigation)
"Clojure"
user=> @languageUnderInvestigation
"Clojure"
user=>
Listing 61: Retrieving the value of a reference in two ways.
To assign a new value to the reference, the ref-set
method can be used. If one tries to do this without being in a transaction, the following happens:
user=> (ref-set languageUnderInvestigation "Haskell")
IllegalStateException No transaction running clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)
Listing 62: Trying to change the value of a reference while not being in a transaction.
A transaction can be started by using dosync
. Wrapped in a dosync
, the changing of the value succeeds:
user=> (dosync (ref-set languageUnderInvestigation "Haskell"))
"Haskell"
user=> @languageUnderInvestigation
"Haskell"
Listing 63: Altering the value of a reference from within a transaction.
Agent are entities that have state and where that state could be modified asynchronously where the agent handles the threading and locking automatically by itself and guarantees that two threads don't alter the state concurrently.
An agent is created using the agent
keyword. As with references, it it is a good idea to assign the agent to variable when creating it so that it is easy to access later on. When creating an agent the inital state could be provided:
user=> (def doubleOhSeven (agent 7))
#'user/doubleOhSeven
Listing 64:Creating an agent.
The state of the agent can then be altered by send
:ing functions to it to execute. Let's create a function that computes the square of in integer
user=> (defn square [i] (* i i))
#'user/square
Listing 65: Method that calculates the square of an integer.
Now we can tell the agent to changes its value by executing square
:
user=> (send doubleOhSeven square)
#object[clojure.lang.Agent 0x314af27 {:status :ready, :val 49}]
Listing 66: Altering the state of an agent by sending it a function to exeute.
The value of an agent can, just like the value of a reference, be retrieved by using @
:
user=> @doubleOhSeven
49
Listing 67: Retrieving the value of an agent.
Reading from an agent, or a reference, will never block. It always returns immediately. It is therefore not possible to determine if an update of the contained value is in progress.
Futurres are something that exist in other languages as well. The Clojure version of a future defines a set of operations that are executed in another thread. The future is created asynchronously and the call to create it returns immediately. The dereferencing operation, however, is synchronous and might hang if the operations that were specified at creation time is not finished. When they have completed, the dereferencing call returns with the result, which is the value of the last operation the future executed. An example:
user=> (def calculateMeaningOfLife (future (Thread/sleep 7000) 42))
#'user/calculateMeaningOfLife
user=> @calculateMeaningOfLife
42
Listing 68: Calculating the meaning of life using a future.
In the code above, we use the sleep()
method in java.lang.Thread
to simulate that it takes a little time to calculate the value to return.
- The names of most predicate functions end with a question mark but
some
does not.
- The not equals operator is
not=
.
(defn fizzBuzz []
(loop [counter 1]
(if (<= counter 100)
(do
(if (= (rem counter 3) 0) (print "Fizz"))
(if (= (rem counter 5) 0) (print "Buzz"))
(if (and (not= (rem counter 3) 0) (not= (rem counter 5) 0)) (print counter))
(println "")
(recur (+ counter 1))
)
)
)
)
- The syntax! Reverse polish notation is fun!
- The support for indefinite sequences. It really fun to play around with
iterate
, take
, drop
and so on!
- Sometimes there still are a confusing number of parenthesis in a clojure program...
- The
loop - recur
construct is not very...elegant.
I think Clojure is the language, so far, that I've liked the most. It almost feels like an adventure game to try to pussle out the correct order of function calls in order to accomplish what I want to accomplish. I like Clojure so much that I will order a book that only focuses on Clojure right after I've completed this blog entry. I suppose a blog review of that will appear on the blog sometime in Q1 next year. Hopefully. I still have one language left to finish this year. Haskell!
1. Ruby
2. Io
3. Prolog
4. Scala
5. Erlang
[1] Clojure homepage
[2] Leiningen homepage
2015-12-29
Haskell is the functional language. The purest of the pure. The crown in the jewel. The king of programming languages. Well, yes. You get the picture.
It it with awe I start this small journey into the world of pure functional programming. I hope that I will be able to finish it without my self-confidence being too injured :)
Haskell is a statically and strongly type language with a type system that infers the type of almost anything.
There are many, I am told, different versions of Haskell. The one I've tried is called Glasgow Haskell Compile (GHC) and can be downloaded from here.
GHC is started by running the ghci
command. It will open up a shell in which Haskell programs can be entered.
lars@lars-lilla:~$ ghci
GHCi, version 7.8.4: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> 42 + 4711
4753
Prelude>
Listing 1: Executing programs in the GHC Haskell interpreter.
Haskell program stored in a text file (which customarily have an .hs extension) can be loaded into the interpreter using :load
. Let's for example store this is a file square.hs
:
module Main where
square x = x * x
It can be loaded into the Haskell system and run like this: Prelude> :load square.hs
[1 of 1] Compiling Main ( square.hs, interpreted )
Ok, modules loaded: Main.
*Main> square 2
4
Listing 2: Loading functions into the Haskell interperter.
A Haskell program can also be compiled to native code by a compiler, e.g. ghc
and then executed directly. To compile and run a small Hello World-program you can do like this:
lars@lars-lilla:~/programmering/14languages/haskell$ ghc -o hello helloWorld.hs [1 of 1] Compiling Main ( helloWorld.hs, helloWorld.o )
Linking hello ...
lars@lars-lilla:~/programmering/14languages/haskell$ ./hello
Hello World!
Listing 3: Compile and run a Haskell program.
The option -o
determines the name of the output executable.
Haskell uses double dashes -- for single line comment:
*Main> -- This is a singe line comment.
Listing 4: Single line comment.
For multi line comments, {-
is used:
{-
This is a multiline comment.
-}
Listing 5: Multi line comment.
The starting point of a stand-alone Haskell program is main
in the module Main
(more on modules in a separate section further down). It should have a return type of IO
.
To output a string to the console putStr
or putStrLn
is used. This means that a hello world program can be written like this:
main = putStrLn "Hello World!"
...
Hello World!
Listing 6: Hello world in Haskell.
Strings are enclosed in double quotes and characters in single quotes:
Prelude> "This is a string."
"This is a string."
Prelude> 'A'
'A'
Listing 7: Enclosing of strings and single characters.
A string is just a list of characters:
Prelude> ['T','h','i','s',' ','i','s',' ','a',' ','s','t','r','i','n','g']
"This is a string"
Listing 8: String as a list of characters.
Strings can be concatenated using the ++
operator:
Prelude> "This" ++ " is" ++ " a" ++ " string"
"This is a string"
Listing 9: Concatenating strings using ++.
Just like in Clojure let
binds a value to a variable. To assign 42 to a variable, one can use the following:
Prelude> let meaning_of_life = 42
Prelude> meaning_of_life
42
Listing 10: Assigning a value to a variable.
Haskell supports the following boolean comparison operators:
And, or and not is written like this in Haskell: &&
, ||
and not
The boolean literals in Haskell are True
and False
.
Tuples are like fixed size, immutable lists. A tuple is created by enclosing a comma separated number of items in parenthesis. E.g. Prelude> (1,2,3)
(1,2,3)
Listing 11: Defining a tuple.
The syntax for creating a list is like one expects: [1,2,3]
.
Lists support the usual head
and tail
operations:
Prelude> let l = [1,2,3]
Prelude> head l
1
Prelude> tail l
[2,3]
Listing 12: The head and tail operations of a list.
It is also possible to assign the head and tail to separate variables using the (h:t)
construct where the head of the list is assigned to h
and the tail to t
.
Prelude> let (h:t) = [1,2,3]
Prelude> h
1
Prelude> t
[2,3]
Listing 13: Assigning the head and tail of a list to variables.
We can use this construct to define recursive functions. E.g. a function that computes the sum of the elements of a list can look like this:
sumOfList [] = 0
sumOfList (h:t) = h + sumOfList t
Listing 14: Function that computes the sum of the elements of a list.
It can be used like this: Prelude> :load sumOfList.hs
[1 of 1] Compiling Main ( sumOfList.hs, interpreted )
Ok, modules loaded: Main.
*Main> sumOfList [1,2,3]
6
Listing 15: Using the sumOfList function.
The :
operator can also be used to construct a list.
Prelude> 1:[1,3]
[1,1,3]
Listing 16: Using : to add an element as the head of a list.
To append to the end of a list you use the ++
operator. It appends the elements in the list on the right of the operator to the list on the left of the operator.
*Main> [1,2,3] ++ [4]
[1,2,3,4]
Listing 17: Using ++ to append elements to a list.
Ranges can be used to construct a list. The syntax is like this [startPoint..endPoint]
. E.g.
Prelude> [1..5]
[1,2,3,4,5]
Listing 18: Using a range to create a list.
As you can see, the default increment is 1, but it is possible to specify another increment. This is done by specifying the next element in the range after the starting element. I.e. one specifies the two first elements in the sequence. By comparing these two elements, Haskell figures out what the increment are intended to be. That's clever.
An example on how an increment can be set using this method:
Prelude> [1,3..11]
[1,3,5,7,9,11]
Listing 19: Specifying an increment in a range.
A range can be unbounded. This is done by omitting the endpoint:
[1..]
. The output from this would consume a lot of space, so let's move on to how one can use an unbound range. This can be done by using take
to limit the unbounded range: Prelude> take 10 [1..]
[1,2,3,4,5,6,7,8,9,10]
Listing 20: Extracting a number of elements from an unbounded range.
To define a function in the shell, you use let
just as when assigning values to a variable.
Prelude> let square x = x * x
Prelude> square 3
9
Listing 21: Defining a function in the shell.
We see that we don't have to surround the parameters with parenthesis.
The syntax is thus let function_name parameters = body
The parameters in the parameter list should be separated by spaces. E.g. to create and invoke a function taking two parameter the following could be used:
Prelude> let times x y = x * y
Prelude> times 3 4
12
Listing 22: A function taking two arguments.
When writing Haskell programs in files instead of in the shell, the syntax is a little bit different. To define a function then, you supply two things: a type definition and the actual body. The previous function can look like this if defined in a separate file:
times :: Int -> Int -> Int
times x y = x * y
Listing 23: A function defined in a separate file.
In the example above times :: Int -> Int -> Int
is the type declaration and times x y = x * y
is the body.
As can be seen in the example, the parameters in the type declaration should be separated by ->
. The return type of the function is the type to the right of the rightmost ->
.
You can check the type of the function using :t functionName
. E.g. to check the type the times
function we just defined, we can use the following:
*Main> :t times
times :: Int -> Int -> Int
Listing 24: Checking the type of a function
In Haskell if
is a function. That means that it must return a value and implies that an if-clause must have an else part. The syntax is similar to many other languages. Let' try it:
Prelude> if (True) then "TRUE" else "FALSE"
"TRUE"
Prelude> if (False) then "TRUE" else "FALSE"
"FALSE"
Listing 25: Basic if-clause.
As it is a functional language recursion have a really prominent place in the Haskell toolbox. We can implement a basic factorial function like this:
factorial n = if n <= 0 then 1 else n * factorial (n-1)
Listing 26: Function determining the factorial of a number. Written using basic recursion.
Recursive functions could also use another two features:
Pattern matching we've encountered several times before in the series. One simply lists a number of versions of a function, each taking different input. When the function is called, Haskell tries to match the actual input to the different versions of the function and invokes the first that matches. Note that the order the versions are listed is important. Haskell starts with the top one and works towards the bottom of the list.
When pattern matching is used, the factorial program can look like this:
factorial 0 = 1
factorial n = n * factorial(n - 1)
Listing 27: Function determining the factorial of a number using pattern matching.
Guards are boolean conditions connected to a piece of code. If the boolean condition is fulfilled, the piece of code gets executed, otherwise it won't. Using guards we can change the order of the clauses compared to the factorial program that uses pattern matching:
factorial n
| n > 0 = n * factorial(n - 1)
| otherwise = 1
Listing 28: Function determining the factorial of a number using guards.
We see that each guard clause starts with a bar |
, then the piece of code follows after a =
. A catch-all clause called otherwise
can be added in the end to catch those calls that haven't matched any of the explicit guards.
Namespacing in Haskell can be done via modules. You create a module by adding a module
statement in the beginning of a source file. For example, if we would like to package our factorial function in a module we can do it like this:
module FactModule where
fact 1 = 1
fact n = n * fact (n - 1)
Listing 29: Packaging the factorial function into a module
A module can then be imported into another module, or into the shell, using the import
keyword:
module Main where
import FactModule
main = print (fact 5)
Listing 30: Importing the FactModule module into another module.
Note that the import
comes after the module
declaration!
We can test drive the code above by loading the TestModule
into the shell:
Prelude> :load TestModule.hs
[1 of 2] Compiling FactModule ( FactModule.hs, interpreted )
[2 of 2] Compiling Main ( TestModule.hs, interpreted )
Ok, modules loaded: Main, FactModule.
*Main> main
120
Listing 31: Test driving the TestModule.
Note that I've stored the FactModule
in a file named FactModule.hs
and the TestModule
in the file TestModule
. It's not necessary to follow this pattern, but it seems easier because then the shell will be able to find the files containing the modules without any tweaking.
Haskell standard library contains a lot of useful modules which can be accessed just by importing them. For example, the Data.Char
module contains a function isDigit
that can determine if a character represents a digit or not. That function can be used by importing the Data.Char
as in the following example:
module Main where
import Data.Char
rev [] = []
rev (h:t) = (rev t) ++ [h]
stringToNumber [h] base = if (isDigit h) then (digitToInt h) * base else 0
stringToNumber (h:t) base = if (isDigit h) then ((digitToInt h) * base) + (stringToNumber t (base * 10)) else (stringToNumber t base)
sToN l = stringToNumber (rev l) 1
Listing 32: Importing a module from the standard library.
In the previous example, another function, stringToNumber
, from the Data.Char
module is also used.
Strings can be printed out to the console using putStr
. Input can be read using getLine
.
So how do we create a function that for example prints several messages to the console? Or read some input, process it, read some more input and print a message based on both entered values? That can be tricky to accomplish just using the Haskell stuff that we've covered so far. do
notation to the rescue!
Using do
you can program almost imperatively inside a function. You start a function body with do
, add a number of statements separated by semi-colons, and end with a return
statement. An example:
module Main where
readName = do putStr "Enter you name: ";
name <- getLine;
putStr "Enter you age: ";
age <- getLine;
return ("Hi, " ++ name ++ " age: " ++ age)
Listing 33: Function using do to do several things sequentially.
The function can then be used like this:
*Main> :load readName.hs
[1 of 1] Compiling Main ( readName.hs, interpreted )
Ok, modules loaded: Main.
*Main> readName
Enter you name: Lars
Enter you age: 42
"Hi, Lars age: 42"
Listing 34: Using the readName function.
Just like Python and Erlang, Haskell supports list comprehensions, and just like in those languages list comprehensions in Haskell consists of two basic parts: an expression and generators/filters. The generators and filters determines what data that should be passed on to the expression. The expression performs operations on that data to produce the elements that are included in the result. Just like in Python and Erlang.
In Haskell the expression is to the left, then comes a |
and to the right of that the generators and filters. Like this:
[expression | generatorsAndFilters]
The variables used in the expression are defined in the generator section by using the arrow syntax: [x | x <- generator]
Let's exemplify. To compute a list of the squares of a number of integers this snippet could be used:
Prelude> [x^2 | x <- [1,2,3,4,5]]
[1,4,9,16,25]
Listing 35: Basic list comprehension.
Several variables can be generated by the generator by separating the variables by commas:
Prelude> [x*y | x <- [1,2,3,4,5], y <- [10,20]]
[10,20,20,40,30,60,40,80,50,100]
Listing 36: List comprehension using two variables.
Filters can be applied to the generator just by adding boolean conditions after the generators:
Prelude> [x*y | x <- [1,2,3,4,5], y <- [10,20], y < 15]
[10,20,30,40,50]
Listing 37: List comprehension including a filter.
Several filters can be separated by commas:
Prelude> [x*y | x <- [1,2,3,4,5], y <- [10,20], x > 2, y < 15]
[30,40,50]
Listing 38: List comprehension including two filters.
Sometimes we want to chain methods together. I.e. if we have functions a
and b
we can take the output from a
and use it as indata to b
. In code this is usually expressed like this: b(a())
. Haskell includes a special syntax for this kind of function chaining: b . a
.
For example, if we have a list on which tail we want to apply head
(i.e. we want to find its second element), we can do like this: Prelude> (head . tail) [1,2,3]
2
Listing 39: Composing two functions together.
Of course, composition is not limited to only two functions. We can add more functions if we want:
Prelude> (head . tail . tail) [1,2,3]
3
Listing 40: Using three functions in a composition.
It is easy to create an anonymous function in Haskell and the syntax is minimalistic: (\param1 param2...) -> body)
An anonymous function that computes the square of an integer can look like this:
(\n -> n*n)
Listing 41: Anonymous function to compute the square of an integer.
To invoke the anonymous function you just pass the inparameters after the definition of the function:
Prelude> (\n -> n*n) 13
169
Listing 42: Invoking an anonymous function.
A locally scoped function is a function that is defined within a function. The syntax is that use first use the "inner" function within the enclosing function, just as if it already has been defined, then you use the keyword where
after which you define the function. An example will perhaps make it more clear. Let's say that we want to define a function that quadruples a number. Such a function can be implemented by applying a function that squares the input number twice. We therefore start implemeting a quadruple function that just calls a square
function twice (using composition). The square
we define in the where
clause:
module Main where
quadruple x = (square . square) x
where square y = y * y
Listing 43: Defining a locally scoped function.
The above function can be loaded and tested in the ghc shell like this:
*Main> :load quadruple.hs
[1 of 1] Compiling Main ( quadruple.hs, interpreted )
Ok, modules loaded: Main.
*Main> quadruple 2
16
Listing 44: Loading end executing the quadruple function.
As expected, being a functional language, Haskell includes a plethora of higher order functions. map
is one of them.
The functionmap
does what it usually does, maps a function to every element in a list. To compute a list of the squares of a list of integers, the following code snippet could be used:
Prelude> map (\n -> n*n) [1,2,3,4,5,6,7,8,9]
[1,4,9,16,25,36,49,64,81]
Listing 45: Using map to compute the squares of the elements in a list.
filter
is another of the standard methods in functional languages. It goes through an input list, applied a predicate to each item and returns a new list containing the elements for which the predicate returned true. In Haskell, a usage of filter
can look like this:
Prelude> filter (\x -> (mod x 2) == 0) [1,2,3,4,5,6,7,8,9]
[2,4,6,8]
Listing 46: Using filter to extract all even numbers from a list.
To perform some kind of aggregation operation on a list, the foldl
and foldr
methods can be used. They both take a function and a start value as parameters. The function should itself take two params the first will be set to the element of the list, the second will be the accumulated value. The difference between foldl
and foldr
is that foldl
starts the processing of the list from the left while foldr
starts from the right.
As an example, let's sum the elements in a list of integers using first foldl
and then foldr
:
Prelude> foldl (\elementValue currentSum -> currentSum + elementValue) 0 [1,2,3,4,5,6,7,8,9]
45
Prelude> foldr (\elementValue currentSum -> currentSum + elementValue) 0 [1,2,3,4,5,6,7,8,9]
45
Listing 47: Using foldl and foldr to sum the integers in a list.
There also exist versions of foldl
and foldr
that applies a binary operator instead of a function to the elements, nicely paired together, in the list. These versions are called foldl1
and foldr1
. Let's rewrite the examples above to use them instead of the regular versions:
Prelude> foldl1 (+) [1,2,3,4,5,6,7,8,9]
45
Prelude> foldr1 (+) [1,2,3,4,5,6,7,8,9]
45
Listing 48: Using foldl1 and foldr1 to sum the integers in a list.
In Haskell you can create a function that calls another function, but only passes some of the parameters that the called function needs. The first function is called a partially applied function. I suppose that it is called that because it only applies some of the parameters to the function it calls. The partially applied function can then itself be called with the remaining parameters, so that in the end, all parameters needed by the second function have values and the function can be executed. Let's exemplify.
Suppose that we a method that can compute x^y called raisedTo
. Let's say that we wanted to create a function that can square numbers. That could be done by creating a partially applied function taking one param that calls raisedTo
supplying 2 for the y value:
Prelude> let raisedTo x y = x ^ y
Prelude> let square x = raisedTo x 2
Prelude> square 5
25
Listing 49: Creating a partially applied function to square a number.
In fact, the process of creating partially applied function is how Haskell handles all functions having more than one parameter. For each parameter more than the first, Haskell creates a partially applied function that it lets the surrounding method call with the parameter in question. This way, all the "extra" parameters are handled one by one until we end up with an outer function that only takes one parameter. When calling the outermost function with all parameters, it will call the first wrapped function with the last param. That function will then call the function it wraps with its param and so on. This process of calling partially applied functions is called currying.
That Haskell uses partially applied functions to handle functions taking more than one parameter can be seen if one checks the type of such a function using :t
.
Prelude> let times x y = x * y
Prelude> :t times
times :: Num a => a -> a -> a
Listing 50: Checking the type of a function taking two parameters.
We see that instead of saying that times
is a function taking two numbers as params and returns a number, it says that times takes one parmeter, applies one function to it and that the result of that function call is the result of the original call.
To mimic this way of handling functions taking more than one param, we can rewrite an invocation of times 3 4
using an anonymous function taking one param which is multiplied by 3. This anonymous function is then invoked with the param 4. Let's see how to do this:
Prelude> (\x -> x * 3) 4
12
Listing 51: Using an anonymous function to compute 3 * 4.
In Haskell, as in Clojure, you can create infinite lists and, also just like in Clojure, you work on then using lazy evaluation techniques. We've already seen the take
function in the section on ranges, but there are also other functions like drop
and head
that you can use to extract parts of an infinite sequence.
To create an infinite sequence, you can either use the range
function or you can write a function that recurses infinitely, e.g.
module Main where
infiniteSequence start = start : (infiniteSequence (start + 1))
Listing 52: Function creating an infinite sequence.
The function above creates an infinite sequence of numbers starting with a start number and where the next number always is one greater that the previous one. We can use the function like this:
Prelude> :load infiniteSequence.hs
[1 of 1] Compiling Main ( infiniteSequence.hs, interpreted )
Ok, modules loaded: Main.
*Main> take 7 (infiniteSequence 10)
[10,11,12,13,14,15,16]
Listing 53: Using the function that creates an infinite sequence.
Using infinite sequences we can quickly build a cool function that generates the Fibonacci sequence. To to that we need a function that given two consecutive numbers in the sequence, generates all following numbers. Such a method can look like this: fibonacciNumbersFrom x y = x : (fibonacciNumbersFrom y (x + y))
.
We then create a function that generates the full sequence by supplying the first two numbers in the series to fibonacciNumbersFrom
. Like this: fibonacciSequence = fibonacciNumbersFrom 1 1
Finally we create a function that returns the n:th number in the series by taking the n first numbers, dropping the first n - 1 of those and returning the single remaining number: fibonacci x = head (drop (x - 1) (take x fibonacciSequence))
The whose program look like this:
module Main where
fibonacciNumbersFrom x y = x : (fibonacciNumbersFrom y (x + y))
fibonacciSequence = fibonacciNumbersFrom 1 1
fibonacci x = head (drop (x - 1) (take x fibonacciSequence))
Listing 54: Cool Fibonacci program.
Let's try it:
*Main> fibonacci 1
1
*Main> fibonacci 2
1
*Main> fibonacci 3
2
*Main> fibonacci 1000
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875
Listing 55: Test driving the cool Fibonacci program.
Seems to work fine ;)
- The import statements in a module must appear after the
module
declaration, not before like in Java.
module Main where
checkModThreeAndFive :: Int -> (IO (), Int)
checkModThreeAndFive n = if ((mod n 3 == 0) && (mod n 5 == 0)) then (putStrLn "fizzBuzz" >> io, retValOne) else (io2, retValTwo)
where (io, retValOne) = loop (n+1)
(io2, retValTwo) = checkModThree n
checkModThree :: Int -> (IO (), Int)
checkModThree n = if (mod n 3 == 0) then (putStrLn "fizz" >> io, retValOne) else (io2, retValTwo) where
(io, retValOne) = loop (n+1)
(io2, retValTwo) = checkModFive n
checkModFive :: Int -> (IO (), Int)
checkModFive n = if (mod n 5 == 0) then (putStrLn "buzz" >> io, retVal) else (print n >> io, retVal) where
(io, retVal) = loop (n+1)
loop :: Int -> (IO (), Int)
loop n = if n <= 100 then (io, retVal) else (return (), 0) where
(io, retVal) = checkModThreeAndFive n
main = fst $ loop 1
- Infinite sequences are cool.
- Guards in functions. Sometimes problems can be expressed really nicely using guards in a function.
- The error messages from the compiler is often very hard to understand. At least for a beginner.
- The learning curve. It is quite steep.
- Some things that are really easy to accomplish in most other languages, like writing a FizzBuzz program, are really hard (for a beginner at least) to do in Haskell.
This was the last language in the first part of the series, i.e. Haskell was the last language in the book Seven Languages in Seven Weeks. Now I will take a short break before continuing with the eight language. Among other things, I'll write a review of Seven Languages in Seven Weeks. I plan on starting with the next language in february or so. See you next year!
1. Ruby
2. Io
3. Prolog
4. Scala
5. Erlang
6. Clojure
[1] Haskell homepage