Computer Science Canada For Cervantes: quick O'Caml tour |
Author: | wtd [ Thu Oct 27, 2005 7:58 pm ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Post subject: | For Cervantes: quick O'Caml tour | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Conditionals The basic conditional looks like this:
The "else" can be optional, but only if expression1 returns unit. For instance:
Since O'Caml is statically typed, both expression1 and expression2 must have the same type.
But, what if you want multiple expressions to be evaluated? Both expression1 and expression2 can only be a single expression. However, grouping multiple expressions together with "begin...end" or parentheses makes them one big expression.
What if I want "else if"? Well, as noted previously, expression1 and expression2 can only be a single expression. Well, here's a surprise for you: an entire if...then...else..." thing-a-ma-jig is a single expression.
Another example of this concept of conditionals as a single expression in action:
Loops Loops in O'Caml are deviously simple. You won't use them much as you become more familiar with the language, though. First we have the basic "for" loop. It increments from a starting value to an terminating value and evaluates a set of expressions each time.
We also have a "while" loop.
Why wouldn't you use these? Because they can't compare to the beauty of:
Or even better:
Or perhaps:
Pattern Matching This often takes the form of something similar to the famous "switch" or "case". Let's look at an example:
The underscore means "anything... I don't really care." If I did care, I could use an actual name.
Again, since O'Caml is statically typed, the expression being evaluated (in the above case "a") will always be of one specific type. That means all of the patterns have to be of the same type. Try to mix ints and chars, for instance, and it won't run.
If we try to write a non-exhaustive matching, O'Caml does a good job of pointing out this potentially huge source of problems. That it catches this at that point is beautiful. Other languages would not say a word and then try to clean up the mess later when it actually becomes a problem.
Tuples Consider tuples to be a fixed-length, immutable, heterogenous list. A simple tuple might hold a set of coordinates in three dimensional space.
Every tuple has a very specific type, which is represented by the types of its components, separated by asterisks.
We can use tuples in matching.
Pattern matching also applies to "let" bindings, and this is one place it's phenomenally useful (among others).
The comma operator creates a tuple. It should be noted, that if you want to be as clear as possibly, surround the tuple with parentheses.
I take exception to that! In that last match example you saw an exception: Match_failure. So, how do we handle exceptions in O'Caml? Why, with the "try" keyword, of course.
This is very similar to "match...with...". The first thing to notice is that an entire "match...with..." is a single expression. So, we try an expression, and when that fails, we handle the exception with something that looks very much like pattern matching. An exception in O'Caml can have one or zero values associated with it. If you'd like multiple values, you can tie them together into a single tuple. Match_failure does that, and we can pattern match on that tuple. Or we can say we don't give a flying rat's butt and just use underscore as a pattern that matches anything. We could also just replace the entire exception with an underscore, so that it handles any exception.
We can easily create our own exceptions.
Lists List are immutable, variable length, homogenous collections. They can be easily represented in code in literal form.
Perhaps the best thing about lists has to do with the :: operator, and how it realtes to pattern matching. The :: operator will take a value, and a list, and create a new list with that value on the front.
Lists are inherently recursive data structures. The previous example could be expressed as:
Or, going further:
We see here that a list is either an empty list, or some value tacked onto the front of a list. We can use this fact when we pattern match.
You'll notice that I used underscore again to represent the rest of the list, since I had no use for that information. Functions Since we've looked at a recursive data structure (lists), it's about time to look at functions, and eventually recursive functions. All functions must take at least one argument, which if nothing else is () or "unit".
Functions can, however, take multiple arguments.
We can also have multiple expressions inside a function.
There's much much more to say about functions, but first let's look at recursive functions a bit.
The reason this code wouldn't work is that for normal functions, the function itself doesn't know about the name you've given to it. This would work if we'd previously created a "say_hello_forever" function, but in that case it would be calling the old "say_hello_forever" function.
We can fix this with the "rec" keyword.
Of course, that's silly. We don't want infinite loops, so we need a way to tell the function to stop calling itself. A conditional or match will work just fine for this.
But it's silly to have recursive functions that just "do" things rather than returning some useful value. Remember how I mentioned that lists are inherently recursive, and that :: can be used to create lists?
If we step through what's happening in "range 1 5" we see:
Variant Types Let's look at a variant type. Let's say I want my own version of the standard list. I'll have it store ints.
Using this is a piece of cake, since Node and Empty are the necessary constructors.
Then writing a recursive function to deal with this is a piece of cake.
Of course, the built-in list can work on anything... not just ints.
The "'a" in the above indicates a generic parameter. It can stand in for any type. The function to_list nicely demonstrates pattern matching and variant types. |
Author: | Naveg [ Thu Oct 27, 2005 8:12 pm ] |
Post subject: | |
Great intro! And just in time, grab your copy of 3.09.0, released today! http://caml.inria.fr/download.en.html |
Author: | wtd [ Thu Oct 27, 2005 8:15 pm ] |
Post subject: | |
Thanks for the link. |
Author: | Cervantes [ Fri Oct 28, 2005 5:33 pm ] |
Post subject: | |
Thank you, wtd! I shall read it soon. |
Author: | wtd [ Fri Oct 28, 2005 6:28 pm ] | ||||
Post subject: | |||||
Commenting
Oh, and they nest, unlike C-style comments.
|
Author: | wtd [ Sat Oct 29, 2005 1:13 pm ] | ||||||||||
Post subject: | |||||||||||
Mutable Values O'Caml, by default, makes things constant... immutable. O'Caml is not a pure functional programming language, though, and also offers the ability to create mutable values. I've used this previously in a loop, to have a mutable counter.
If I look at "ref 0" a bit closer...
So the "ref" function created a new reference to zero. How can we manipulate this? The prefix ! operator extracts the value from the reference.
The := operator, which looks familiar from a lot of different programming languages, mutates the value in the reference.
Thus incrementing the reference:
|
Author: | wtd [ Sat Oct 29, 2005 1:24 pm ] | ||||||||||||
Post subject: | |||||||||||||
Record Types So, how does "ref" work? Is it magic? Nope. It's a record, which is why this topic is overdue for discussion. Let's say we want to reinvent "ref". We need a record with a single field.
Oh, and we'll want a function to create a new rref.
And a function to retrieve the value stored in it.
Now we can actually create a rref.
Now, how do I mutate that value? I'd need a "setref" function.
That's not quite right. It didn't work because, as the error message explains, rcontents is not mutable. Let's make it mutable.
|
Author: | wtd [ Sat Oct 29, 2005 6:04 pm ] | ||||
Post subject: | |||||
;; There have been lots of these in the above examples. The O'Caml toplevel (interactive interpreter) requires them so it knows when you're done entering code. However, in a complete program you're far less likely to need them. The "let" keyword is a fairly common one in O'Caml, and is a powerful separator. We can see some of this in the toplevel.
There are places where this doesn't work, though.
In the above, O'Caml thinks you're trying to pass "wooble" and "()" to "print_endline". |
Author: | wtd [ Tue Nov 01, 2005 12:46 pm ] | ||||||||||||||
Post subject: | |||||||||||||||
File I/O Let's open a text file for reading:
Let's read a line from it:
And then we'll close it:
Let's open a file for output, write to it, then close it:
Now, how do we read all of the lines in a file? Well, recursion and exception handling gives us all the tools we need. First let's see what happens when we try to read from a file indefinitely.
When we try to read past the end of the file, an End_of_file exception is thrown. So, let's write a simple recursive function to read all lines.
Same thing, because we never handled the exception. Plus, in our recursive function we never specified a place where the recursion would stop. Let's kill two birds with one stone.
|
Author: | wtd [ Tue Nov 01, 2005 11:18 pm ] | ||||||||||||
Post subject: | |||||||||||||
Modules The fundamental means of code re-use is the module. The creation of modules is quite easy in O'Caml. A simple module:
We can then easily use the bindings from the module.
Of course, it's possible to eliminate the need for the module name prefixed.
We can create shortened names for modules.
Modules can be nested.
Of course, it's possible to control what is exported from a module.
|
Author: | wtd [ Fri Nov 04, 2005 3:02 pm ] | ||||||||||||||||
Post subject: | |||||||||||||||||
Objects Yes, O'Caml permits object-oriented programming. So let's look at the simplest possible object.
Of course, that does nothing. It doesn't even create a class. It's an object that stands on its own with no class. Let's create a class.
And we'll want to be able to create a new name object.
Of course, we'll want to pass in some parameters when we create the object: a first and last name.
We'll probably want to store those values inside the object.
Well, we still can't do anything with this. Those values are not accessible from outside the object. Let's provide accessor methods.
The problem here is that O'Caml uses type inferencing to figure out what type everything should be, but it needs to see how something is being used to figure that out. We haven't provided enough use. Let's give it more to think about. Let's create a method which formats the first and last name into a full name.
Now we can actually do something with this object.
|
Author: | MysticVegeta [ Fri Nov 04, 2005 6:20 pm ] |
Post subject: | |
How fast do you type? |
Author: | Cervantes [ Fri Nov 04, 2005 7:15 pm ] |
Post subject: | |
I'm at variant types. Thanks for all of this, wtd. You truly are amazing. |
Author: | wtd [ Fri Nov 04, 2005 7:44 pm ] |
Post subject: | |
MysticVegeta wrote: How fast do you type?
Sadly, not very fast. |
Author: | wtd [ Sat Nov 05, 2005 2:00 am ] | ||||||
Post subject: | |||||||
The option type Varant types, when combined with generics make something really special possible. Let's look at a task. I want to search a list for a value, and return the index of that value. Pretty straighforward, right? What if you don't find it? Raise an exception? Well, that's pretty expensive and causes a lot of trouble. We could check to see if the list has the value first, but that means iterating over the list multiple times if it is there. But... what if we could return a value that indicates "nothing"? Zilch, nada... squat. Or... the right value. We can. The option type, with its None and Some constructors allows just that.
We can then pattern match the return and take different courses of action based on whether it's Some something, or None.
|
Author: | wtd [ Sat Nov 05, 2005 2:10 am ] | ||||||||||
Post subject: | |||||||||||
Pattern matching and "when" Last post had this example:
That's a mess. Let's clean it up by adding some conditions to our pattern match.
Much better.
We know that:
Will match a list where x is the first element, and where we don't care what the rest of the list is. And because of the "when" part of this, this pattern will only match if "x" is the value we're looking for. Since we now have the value we're looking for, we don't need to look at the rest of the list, so we can use the underscore.
If we get past that pattern, then we know that the first element isn't what we're looking for, so we don't have to give a darn about it. |
Author: | wtd [ Sun Nov 06, 2005 3:35 pm ] | ||||||||||||||
Post subject: | |||||||||||||||
Hashtables Someone coming from Ruby, Python or Perl has seen hashes (or "dictionaries" in Pythonspeak), and should be well acquainted with their power. You'll be happy to know that O'Caml has hashtables as well, as defined by the Hashtbl module. Unfortunately, there isn't a literal syntactic form for a hash, but they're easy enough to manipulate anyway. First off, though, let's create a hashtable.
The 10 simply indicates that we're initially reserving space for ten entries. Now, let's add an entry.
And then we'll look that up again.
What if I try to look up something that's not in the hash?
So I might perhaps handle this like so:
Now here's something fun. Add "Hello" => "world" to the hashtable. Now add "hello" => "ninja" to the table. The "ninja" entry should overwrite "world", correct? Well, not quite. The table holds both. If you do a find for "hello" you'll get "ninja", but "world" is still in there, and using the find_all function instead of find will make that perfectly clear. If we want to achieve the overwriting effect, we can use the replace function. To iterate over a hashtable, for the purpose of printing it out, for instance, we can use the iter function.
One thing notably missing from the Hashtbl module is a function which retrieves all of the values or keys from a hashtable. Seems an odd omission, doesn't it? Yet, the "fold" function makes all of this trivially possible.
Additional functions include: mem - tests for the presence of a key in a hash clear - empties the hashtable remove - remove a key length - returns the length of the hashtable copy - creates a copy of the hashtable |