Posted: Thu Oct 27, 2005 7:58 pm Post subject: For Cervantes: quick O'Caml tour
Conditionals
The basic conditional looks like this:
code:
if condition then expression1 else expression 2
The "else" can be optional, but only if expression1 returns unit. For instance:
code:
# if true then print_endline "hello";;
hello
- : unit = ()
# if true then 42;;
Characters 13-15:
if true then 42;;
^^
This expression has type int but is here used with type unit
Since O'Caml is statically typed, both expression1 and expression2 must have the same type.
code:
# if true then 42 else 27;;
- : int = 42
# if true then 42 else 'z';;
Characters 21-24:
if true then 42 else 'z';;
^^^
This expression has type char but is here used with type int
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.
code:
# if true then begin
print_string "foo ";
print_int 42;
print_endline " bar"
end
else (
print_endline "wooble!";
print_endline "Ninja!"
);;
foo 42 bar
- : unit = ()
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.
code:
# if true then 42 else if true then 35 else 27;;
- : int = 42
Another example of this concept of conditionals as a single expression in action:
code:
# if true then print_endline "Hello" else print_endline "world";;
Hello
- : unit = ()
# print_endline (if true then "Hello" else "world");;
Hello
- : unit = ()
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.
code:
# let my_array = [|1; 45; 16; 2|] in
for index = 0 to Array.length my_array - 1 do
print_int my_array.(index);
print_newline ()
done;;
1
45
16
2
- : unit = ()
We also have a "while" loop.
code:
# let my_array = [|1; 42; 65; 54; 9|]
and counter = ref 0
in
while !counter < Array.length my_array do
print_int my_array.(!counter);
print_newline ();
counter := !counter + 1
done;;
1
42
65
54
9
- : unit = ()
Why wouldn't you use these? Because they can't compare to the beauty of:
code:
# let my_array = [|1; 42; 65; 54; 9|] in
Array.iter (fun x -> print_int x; print_newline ()) my_array;;
1
42
65
54
9
- : unit = ()
Or even better:
code:
# let my_array = [|1; 42; 65; 54; 9|] in
Array.iter (Printf.printf "%d\n") my_array;;
1
42
65
54
9
- : unit = ()
Or perhaps:
code:
# let my_array = [|1; 42; 65; 54; 9|]
and print_func = Printf.printf "%d\n"
in
Array.iter print_func my_array;;
1
42
65
54
9
- : unit = ()
Pattern Matching
This often takes the form of something similar to the famous "switch" or "case".
Let's look at an example:
code:
- : unit = ()
# let a = 42 in
match a with
32 -> "Thirty-two"
| 47 -> "Forty-seven"
| _ -> "Some other funky number";;
- : string = "Some other funky number"
The underscore means "anything... I don't really care." If I did care, I could use an actual name.
code:
# let a = 42 in
match a with
32 -> "Thirty-two"
| 47 -> "Forty-seven"
| n -> string_of_int n;;
- : string = "42"
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.
code:
# let a = 42 in
match a with
32 -> "Thirty-two"
| 47 -> "Forty-seven"
| 's' -> "s"
| n -> string_of_int n;;
Characters 83-86:
| 's' -> "s"
^^^
This pattern matches values of type char
but is here used to match values of type int
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.
code:
# let a = 42 in
match a with
32 -> "Thirty-two"
| 47 -> "Forty-seven";;
Characters 16-77:
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
0
..match a with
32 -> "Thirty-two"
| 47 -> "Forty-seven"..
Exception: Match_failure ("", 52, 2).
# let a = 32 in
match a with
32 -> "Thirty-two"
| 47 -> "Forty-seven";;
Characters 16-77:
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
0
..match a with
32 -> "Thirty-two"
| 47 -> "Forty-seven"..
- : string = "Thirty-two"
Tuples
Consider tuples to be a fixed-length, immutable, heterogenous list.
A simple tuple might hold a set of coordinates in three dimensional space.
code:
(34, 57, 82)
Every tuple has a very specific type, which is represented by the types of its components, separated by asterisks.
code:
# (34, 57, 82);;
- : int * int * int = (34, 57, 82)
We can use tuples in matching.
code:
# match (34, 57, 82) with
(34, 57, 82) -> "Confound you Bond! You found my doomsday weapon!"
| (34, 57, n) -> "Close, but your personal jetpack doesn't have enough gas."
| _ -> "Good luck next time, double-oh-loser!";;
- : string = "Confound you Bond! You found my doomsday weapon!"
code:
# match (34, 57, 74) with
(34, 57, 82) -> "Confound you Bond! You found my doomsday weapon!"
| (34, 57, n) -> "Close, but your personal jetpack doesn't have enough gas."
| _ -> "Good luck next time, double-oh-loser!";;
- : string = "Close, but your personal jetpack doesn't have enough gas."
Pattern matching also applies to "let" bindings, and this is one place it's phenomenally useful (among others).
code:
# let a, b, c = 34, 57, 82;;
val a : int = 34
val b : int = 57
val c : int = 82
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.
code:
# let (a, b, c) = (34, 57, 82);;
val a : int = 34
val b : int = 57
val c : int = 82
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.
code:
# try (let a = 42 in
match a with
32 -> "thirty-two"
| 47 -> "forty-seven")
with Match_failure _ -> "some other number";;
Characters 21-82:
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
0
..match a with
32 -> "thirty-two"
| 47 -> "forty-seven".
- : string = "some other number"
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.
code:
# try (let a = 42 in
match a with
32 -> "thirty-two"
| 47 -> "forty-seven")
with _ -> "some other number";;
Characters 21-82:
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
0
..match a with
32 -> "thirty-two"
| 47 -> "forty-seven".
- : string = "some other number"
We can easily create our own exceptions.
code:
# exception A;;
exception A
# raise A;;
Exception: A.
# exception B of string;;
exception B of string
# raise (B "Hello");;
Exception: B "Hello".
# exception C of string * int;;
exception C of string * int
# raise (C ("foo", 42));;
Exception: C ("foo", 42).
Lists
List are immutable, variable length, homogenous collections. They can be easily represented in code in literal form.
code:
# [1; 2; 3];;
- : int list = [1; 2; 3]
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.
code:
# 1 :: [2; 3];;
- : int list = [1; 2; 3]
Lists are inherently recursive data structures.
The previous example could be expressed as:
code:
# 1 :: 2 :: [3];;
- : int list = [1; 2; 3]
Or, going further:
code:
# 1 :: 2 :: 3 :: [];;
- : int list = [1; 2; 3]
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.
code:
# let lst = [1; 2; 3] in
match lst with
[] -> print_endline "Empty list."
| x::_ -> print_endline (string_of_int x);;
1
- : unit = ()
code:
# let lst = [] in
match lst with
[] -> print_endline "Empty list."
| x::_ -> print_endline (string_of_int x);;
Empty list.
- : unit = ()
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".
code:
# let say_hello () = print_endline "Hello";;
val say_hello : unit -> unit = <fun>
# say_hello ();;
Hello
- : unit = ()
Functions can, however, take multiple arguments.
code:
# let add a b = a + b;;
val add : int -> int -> int = <fun>
# add 1 3;;
- : int = 4
We can also have multiple expressions inside a function.
code:
# let add a b = print_endline "add function"; a + b;;
val add : int -> int -> int = <fun>
# add 1 3;;
add function
- : int = 4
There's much much more to say about functions, but first let's look at recursive functions a bit.
code:
# let say_hello_forever () =
print_endline "Hello";
say_hello_forever ();;
Characters 55-72:
say_hello_forever ();;
^^^^^^^^^^^^^^^^^
Unbound value say_hello_forever
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.
code:
# let a () = 42;;
val a : unit -> int = <fun>
# let a () = print_endline "a"; a ();;
val a : unit -> int = <fun>
# a ();;
a
- : int = 42
We can fix this with the "rec" keyword.
code:
# let rec say_wooble_forever () =
print_endline "wooble";
say_wooble_forever ();;
val say_wooble_forever : unit -> 'a = <fun>
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.
code:
# let rec print_range a b =
if a > b then ()
else (
print_endline (string_of_int a);
print_range (a + 1) b
);;
val print_range : int -> int -> unit = <fun>
# print_range 1 5;;
1
2
3
4
5
- : unit = ()
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?
code:
# let rec range a b =
if a > b then []
else a :: range (a + 1) b;;
val range : int -> int -> int list = <fun>
# range 1 5;;
- : int list = [1; 2; 3; 4; 5]
If we step through what's happening in "range 1 5" we see:
Then writing a recursive function to deal with this is a piece of cake.
code:
# let rec to_list lst =
match lst with
Empty -> []
| Node (v, lst2) -> v :: to_list lst2;;
val to_list : my_list -> int list = <fun>
# let l = Node (4, Node (5, Node (7, Empty))) in
to_list l;;
- : int list = [4; 5; 7]
Of course, the built-in list can work on anything... not just ints.
code:
# type 'a my_list = Node of 'a * 'a my_list | Empty;;
type 'a my_list = Node of 'a * 'a my_list | Empty
# let rec to_list lst =
match lst with
Empty -> []
| Node (v, lst2) -> v :: to_list lst2;;
val to_list : 'a my_list -> 'a list = <fun>
# let l = Node ('r', Node ('z', Node ('a', Empty))) in
to_list l;;
- : char list = ['r'; 'z'; 'a']
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.
Sponsor Sponsor
Naveg
Posted: Thu Oct 27, 2005 8:12 pm Post subject: (No subject)
Great intro!
And just in time, grab your copy of 3.09.0, released today!
Posted: Thu Oct 27, 2005 8:15 pm Post subject: (No subject)
Thanks for the link.
Cervantes
Posted: Fri Oct 28, 2005 5:33 pm Post subject: (No subject)
Thank you, wtd!
I shall read it soon.
wtd
Posted: Fri Oct 28, 2005 6:28 pm Post subject: (No subject)
Commenting
code:
(* Hello, world! *)
Oh, and they nest, unlike C-style comments.
code:
(* Hello (* world *)*)
wtd
Posted: Sat Oct 29, 2005 1:13 pm Post subject: (No 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.
code:
# let my_array = [|1; 42; 65; 54; 9|]
and counter = ref 0
in
while !counter < Array.length my_array do
print_int my_array.(!counter);
print_newline ();
counter := !counter + 1
done;;
1
42
65
54
9
- : unit = ()
If I look at "ref 0" a bit closer...
code:
# let counter = ref 0;;
val counter : int ref = {contents = 0}
So the "ref" function created a new reference to zero. How can we manipulate this?
The prefix ! operator extracts the value from the reference.
code:
# !counter;;
- : int = 0
The := operator, which looks familiar from a lot of different programming languages, mutates the value in the reference.
code:
# counter := 42;;
- : unit = ()
# !counter;;
- : int = 42
Thus incrementing the reference:
code:
# counter := !counter + 1;;
- : unit = ()
# !counter;;
- : int = 43
wtd
Posted: Sat Oct 29, 2005 1:24 pm Post subject: (No 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.
code:
# type 'a rref = { rcontents : 'a };;
type 'a rref = { rcontents : 'a; }
Oh, and we'll want a function to create a new rref.
code:
# let rref value = { rcontents = value };;
val rref : 'a -> 'a rref = <fun>
And a function to retrieve the value stored in it.
code:
# let deref r = r.rcontents;;
val deref : 'a rref -> 'a = <fun>
Now we can actually create a rref.
code:
# let foo = rref 42;;
val foo : int rref = {rcontents = 42}
# deref foo;;
- : int = 42
Now, how do I mutate that value? I'd need a "setref" function.
code:
# let setref r new_value = r.rcontents <- new_value;;
Characters 25-49:
let setref r new_value = r.rcontents <- new_value;;
^^^^^^^^^^^^^^^^^^^^^^^^
The record field label rcontents is not mutable
That's not quite right. It didn't work because, as the error message explains, rcontents is not mutable. Let's make it mutable.
code:
# type 'a rref = {mutable rcontents:'a};;
type 'a rref = { mutable rcontents : 'a; }
# let rref v = {rcontents = v};;
val rref : 'a -> 'a rref = <fun>
# let setref r v = r.rcontents <- v;;
val setref : 'a rref -> 'a -> unit = <fun>
# let deref r = r.rcontents;;
val deref : 'a rref -> 'a = <fun>
# let foo = rref 0;;
val foo : int rref = {rcontents = 0}
# deref foo;;
- : int = 0
# setref foo 42;;
- : unit = ()
# deref foo;;
- : int = 42
wtd
Posted: Sat Oct 29, 2005 6:04 pm Post subject: (No 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.
code:
# let a = 42
let b = 27
let foo () =
print_endline "Hello";
print_endline "world"
let baz = 3.14;;
val a : int = 42
val b : int = 27
val foo : unit -> unit = <fun>
val baz : float = 3.14
There are places where this doesn't work, though.
code:
# let wooble () = print_endline "wooble"
wooble ();;
Characters 16-29:
let wooble () = print_endline "wooble"
^^^^^^^^^^^^^
This function is applied to too many arguments,
maybe you forgot a `;'
In the above, O'Caml thinks you're trying to pass "wooble" and "()" to "print_endline".
Sponsor Sponsor
wtd
Posted: Tue Nov 01, 2005 12:46 pm Post subject: (No subject)
File I/O
Let's open a text file for reading:
code:
# let file_handle = open_in "test.ml";;
val file_handle : in_channel = <abstr>
Let's open a file for output, write to it, then close it:
code:
# let file_handle = open_out "foo.txt";;
val file_handle : out_channel = <abstr>
# output_string file_handle "Hello, world!";;
- : unit = ()
# close_out file_handle;;
- : unit = ()
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.
code:
# let file_handle = open_in "test.ml";;
val file_handle : in_channel = <abstr>
# while true do
let line = input_line file_handle in
print_endline line
done;;
open Graphics;;
let count = ref 0;;
open_graph " 640x480";;
while true do
if button_down () then
let (x, y) = mouse_pos () in
let coords = string_of_int x ^ "x" ^ string_of_int y in
begin
moveto x y;
draw_string coords
end;
count := !count + 1
done
Exception: End_of_file.
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.
code:
# let rec read_all_lines file_handle =
let line = input_line file_handle in
line :: read_all_lines file_handle;;
val read_all_lines : in_channel -> string list = <fun>
# let file_handle = open_in "test.ml";;
val file_handle : in_channel = <abstr>
# read_all_lines file_handle;;
Exception: End_of_file.
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.
code:
# let rec read_all_lines file_handle =
try
let line = input_line file_handle in
line :: read_all_lines file_handle
with
End_of_file -> [];;
val read_all_lines : in_channel -> string list = <fun>
# let file_handle = open_in "test.ml";;
val file_handle : in_channel = <abstr>
# read_all_lines file_handle;;
- : string list =
["open Graphics;;"; ""; "let count = ref 0;;"; "";
"open_graph \" 640x480\";;"; ""; "while true do";
" if button_down () then"; " let (x, y) = mouse_pos () in";
" let coords = string_of_int x ^ \"x\" ^ string_of_int y in";
" begin"; " moveto x y;";
" draw_string coords"; " end;";
" count := !count + 1"; "done"]
wtd
Posted: Tue Nov 01, 2005 11:18 pm Post subject: (No 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:
code:
# module Foo =
struct
let bar = "baz"
end;;
module Foo : sig val bar : string end
We can then easily use the bindings from the module.
code:
# print_endline Foo.bar;;
baz
- : unit = ()
Of course, it's possible to eliminate the need for the module name prefixed.
code:
# include Foo;;
val bar : string = "baz"
# bar;;
- : string = "baz"
We can create shortened names for modules.
code:
# module F = Foo;;
module F : sig val bar : string end
# F.bar;;
- : string = "baz"
Modules can be nested.
code:
- : string = "baz"
# module F =
struct
module G =
struct
let h = "i"
end
end;;
module F : sig module G : sig val h : string end end
# F.G.h;;
- : string = "i"
Of course, it's possible to control what is exported from a module.
code:
# module A : sig
val b : unit -> unit
end = struct
let c = 42
let b () = Printf.printf "%d\n" c
end;;
module A : sig val b : unit -> unit end
# A.b ();;
42
- : unit = ()
# A.c;;
Characters 0-3:
A.c;;
^^^
Unbound value A.c
wtd
Posted: Fri Nov 04, 2005 3:02 pm Post subject: (No subject)
Objects
Yes, O'Caml permits object-oriented programming.
So let's look at the simplest possible object.
code:
# object end;;
- : < > = <obj>
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.
code:
# class name = object end;;
class name : object end
And we'll want to be able to create a new name object.
code:
# let foo = new name;;
val foo : name = <obj>
Of course, we'll want to pass in some parameters when we create the object: a first and last name.
code:
# class name initial_first_name initial_last_name =
object
end;;
class name : 'a -> 'b -> object end
# let foo = new name "Bob" "Smith";;
val foo : name = <obj>
We'll probably want to store those values inside the object.
code:
# class name initial_first_name initial_last_name =
object
val first = initial_first_name
val last = initial_last_name
end;;
class name : 'a -> 'b -> object val first : 'a val last : 'b end
Well, we still can't do anything with this. Those values are not accessible from outside the object. Let's provide accessor methods.
code:
# class name initial_first_name initial_last_name =
object
val first = initial_first_name
val last = initial_last_name
method get_first_name = first
method get_last_name = last
end;;
Characters 5-200:
..... name initial_first_name initial_last_name =
object
val first = initial_first_name
val last = initial_last_name
method get_first_name = first
method get_last_name = last
end..
Some type variables are unbound in this type:
class name :
'a ->
'b ->
object
val first : 'a
val last : 'b
method get_first_name : 'a
method get_last_name : 'b
end
The method get_first_name has type 'a where 'a is unbound
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.
code:
# class name initial_first_name initial_last_name =
object
val first = initial_first_name
val last = initial_last_name
method get_first_name = first
method get_last_name = last
method get_full_name = first ^ " " ^ last
end;;
class name :
string ->
string ->
object
val first : string
val last : string
method get_first_name : string
method get_full_name : string
method get_last_name : string
end
Now we can actually do something with this object.
code:
# let foo = new name "Bob" "Smith" in
print_endline foo#get_full_name;;
Bob Smith
- : unit = ()
MysticVegeta
Posted: Fri Nov 04, 2005 6:20 pm Post subject: (No subject)
How fast do you type?
Cervantes
Posted: Fri Nov 04, 2005 7:15 pm Post subject: (No subject)
I'm at variant types.
Thanks for all of this, wtd. You truly are amazing.
wtd
Posted: Fri Nov 04, 2005 7:44 pm Post subject: (No subject)
MysticVegeta wrote:
How fast do you type?
Sadly, not very fast.
wtd
Posted: Sat Nov 05, 2005 2:00 am Post subject: (No 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.
code:
# let rec find value lst index =
match lst with
[] -> None
| x::xs ->
if x = value then Some index
else find value xs (index + 1);;
val find : 'a -> 'a list -> int -> int option = <fun>
code:
# find 3 [4;3;6;5] 0;;
- : int option = Some 1
# find 3 [4;1;6;5] 0;;
- : int option = None
We can then pattern match the return and take different courses of action based on whether it's Some something, or None.
code:
# match find 3 [4;3;5;6] 0 with
None -> print_endline "Didn't find it."
| Some index -> print_endline ("Found it at: " ^ string_of_int index);;
Found it at: 1
- : unit = ()
# match find 3 [4;1;5;6] 0 with
None -> print_endline "Didn't find it."
| Some index -> print_endline ("Found it at: " ^ string_of_int index);;
Didn't find it.
- : unit = ()