Run the "ocaml" command. You will see the following in a command window.
code:
Objective Caml version 3.10.0
#
Exit it with Ctrl+z or Ctrl+d on Linux.
This will be your primary tool for learning O'Caml. More on that tomorrow.
Day 2
Values
Common types of data to work with in Turing are integers, floating point (or "real") numbers, strings, characters and booleans. In O'Caml they're essentially the same. We can see these in the toplevel easily.
The double semi-colon ends an expression in the O'Caml toplevel. After each line the interpreter shows us the value, along with the type and any name bound to that value. Here "-" indicates the value is bound to no name.
Some simple operations
code:
# 1 + 2 * 3;;
- : int = 7
Note that the order of operations you are used to is maintained.
Operators are not overloaded, as you will see below.
In Turing, you'd write something like the following.
code:
var foo : int := 42
In O'Caml, we'd do much the same with the following (in the toplevel).
code:
# let foo = 42;;
val foo : int = 42
#
The feedback from the toplevel has changed now. It now tells us that what it sees is a value named "foo".
We can go on to use that name in expressions.
code:
# foo + 27;;
- : int = 69
# 84 / foo + 6;;
- : int = 8
# let bar = foo * 2;;
val bar : int = 84
#
Now, experiment with other names and other values.
Day 4
Functions
Let's start simple.
code:
function foo(bar : int) : int
result bar * 3
end foo
Here the types involved in the function "foo" are explicitly delimited.
code:
# let foo bar = bar * 3;;
val foo : int -> int = <fun>
#
The types involved in this O'Caml function are not explicitly stated at all. They are instead inferred based on the usage. Since operators are not overloaded, the only type "bar" could possibly have is "int" and the type the function must return is also "int".
The "int -> int" notation for the type of the value "foo" indicates that it is a function which takes one int and returns an int. This the language can figure out without us needing to tell it.
We can see the type of a function by just entering its name.
code:
# foo;;
- : int -> int = <fun>
#
A few existing functions
Let's say we have a floating point number we want to pass to our function foo. We'd need to explicitly change it. The "int_of_float" function would come in handy.
code:
# foo 3;;
- : int = 9
# int_of_float 4.5;;
- : int = 4
# foo (int_of_float 4.5);;
- : int = 12
# let bar = 6.3;;
val bar : float = 6.3
# foo (int_of_float bar);;
- : int = 18
#
Variables in O'Caml aren't variable. We are simply binding a name to a value. What we have done so far would be more akin to creating constants in Turing. This is a good thing.
This expression may seem unwieldy. Let's use local bindings to clean it up.
code:
# let str = string_of_int (foo (int_of_float bar)) in
print_endline str;;
18
- : unit = ()
# let i = int_of_float bar in
let s = string_of_int (foo i) in
print_endline s;;
18
- : unit = ()
#
Of course, the types involved in local binding are inferred.
Exercise
Write a function which takes an integer argument, converts it to a floating point number, and then prints that number. Use local bindings to store the floating point number and string before using them.
Day 6
Records
Let's define a simple record and a variable holding a record of that type, then we'll print out that information.
code:
type Student :
record
name : string
grade : int
end record
var bob : Student
bob.name := "Bob Smith"
bob.grade := 75
put bob.name, " has a grade of ", bob.grade
And now in O'Caml.
code:
# type student = { name : string; grade : int };;
type student = { name : string; grade : int; }
# let bob = { name="Bob Smith"; grade=75 };;
val bob : student = {name = "Bob Smith"; grade = 75}
# print_string (bob.name ^ " has a grade of ");
print_int bob.grade;
print_newline ();;
Bob Smith has a grade of 75
- : unit = ()
#
Functions with multiple expresions
Let's organize that bit of code.
code:
type Student :
record
name : string
grade : int
end record
var bob : Student
bob.name := "Bob Smith"
bob.grade := 75
procedure student_report (s : student)
put s.name, " has a grade of ", s.grade
end student_report
student_report (bob)
code:
# type student = { name : string; grade : int };;
type student = { name : string; grade : int; }
# let bob = { name="Bob Smith"; grade=75 };;
val bob : student = {name = "Bob Smith"; grade = 75}
# let student_report s =
print_string (s.name ^ " has a grade of ");
print_int s.grade;
print_newline ();;
val student_report : student -> unit = <fun>
# student_report bob;;
Bob Smith has a grade of 75
- : unit = ()
#
Note that just based on the fields we used on "s" allowed O'Caml to infer the type of that argument. But, O'Caml has another trick up its sleeve.
Pattern matching
code:
# let student_report { name=n; grade=g } =
print_string (n ^ " has a grade of ");
print_int g;
print_newline ();;
val student_report : student -> unit = <fun>
# student_report bob;;
Bob Smith has a grade of 75
- : unit = ()
#
Day 7
Conditionals
code:
type Student :
record
name : string
grade : int
end record
var bob : Student
bob.name := "Bob Smith"
bob.grade := 75
procedure student_report (s : student)
if s.grade >= 65 then
put s.name, " passed with a grade of ", s.grade
else
put s.name, " failed with a grade of ", s.grade
end if
end student_report
student_report (bob)
code:
# type student = { name : string; grade : int };;
type student = { name : string; grade : int; }
# let bob = { name="Bob Smith"; grade=75 };;
val bob : student = {name = "Bob Smith"; grade = 75}
# let student_report s =
if s.grade >= 65 then
print_string (s.name ^ " passed with a grade of ")
else
print_string (s.name ^ " failed with a grade of ");
print_int s.grade;
print_newline ();;
val student_report : student -> unit = <fun>
# student_report bob;;
Bob Smith passed with a grade of 75
- : unit = ()
#
A different take
code:
type Student :
record
name : string
grade : int
end record
var bob : Student
bob.name := "Bob Smith"
bob.grade := 75
procedure student_report (s : student)
var msg : string
if s.grade >= 65 then
msg := " passed with a grade of "
else
msg := " failed with a grade of "
end if
put s.name, msg, s.grade
end student_report
student_report (bob)
code:
# type student = { name : string; grade : int };;
type student = { name : string; grade : int; }
# let bob = { name="Bob Smith"; grade=75 };;
val bob : student = {name = "Bob Smith"; grade = 75}
# let student_report {name=n; grade=g} =
let msg =
if g >= 65 then
" passed with a grade of "
else
" failed with a grade of "
in
print_string (n ^ msg);
print_int g;
print_newline ();;
val student_report : student -> unit = <fun>
#
What's going on here?
A conditional in Turing can only have side-effects. It can only do something, like assigning a new value to a variable.
On the other hand, an O'Caml conditional evaluates to a value. In this case, that greatly simplifies binding a value to "msg". We can even distill this down further.
code:
# type student = { name : string; grade : int };;
type student = { name : string; grade : int; }
# let bob = { name="Bob Smith"; grade=75 };;
val bob : student = {name = "Bob Smith"; grade = 75}
# let student_report {name=n; grade=g} =
let msg =
(if g >= 65 then "passed" else " failed") ^ " with a grade of"
in
print_string (n ^ " " ^ msg ^ " ");
print_int g;
print_newline ();;
val student_report : student -> unit = <fun>
# student_report bob;;
Bob Smith passed with a grade of 75
- : unit = ()
#
Functions can be constructed with pattern matching in another way. Guard patterns let us limit a pattern. The expression associated with the first pattern to match the input will be evaluated.
code:
# type student = { name : string; grade : int };;
type student = { name : string; grade : int; }
# let bob = { name="Bob Smith"; grade=75 };;
val bob : student = {name = "Bob Smith"; grade = 75}
# let student_report = function
{name=n; grade=g} when g >= 65 ->
print_string (n ^ " passed with a grade of ");
print_int g;
print_newline ()
| {name=n; grade=g} ->
print_string (n ^ " failed with a grade of ");
print_int g;
print_newline ();;
val student_report : student -> unit = <fun>
# student_report { grade=42; name="Brian" };;
Brian failed with a grade of 42
- : unit = ()
#
Day 8
Writing a program
So far we've evaluated expressions in the interactive environment, but it's about time to move on to writing a program in its entirety.
So, open your favorite text editor, and create a file called "helloworld.ml" with the following code.
code:
print_endline "Hello, world!"
Now, open a terminal window and navigate to the directory where you saved your source file. If you have questions on doing this in Windows, please ask.
We'll now compile it using the following:
code:
ocamlc helloworld.ml -o helloworld
If you're using Windows, you should have it tack a ".exe" onto your executable name.
code:
ocamlc helloworld.ml -o helloworld.exe
That executable can then be run the same way any other executable would be.
Of course, that's exceptionally trivial, but it's a start. Let's incorporate something else.
code:
print_string "Please enter your name: ";;
let name = read_line ();;
print_endline ("Hello, " ^ name ^ "!")
Let's add a few functions in.
code:
let greet_nicely name =
print_endline ("Hello, " ^ name ^ "!");;
let greet_rudely name =
print_endline ("Bugger off " ^ name ^ "...");;
let greet = greet_nicely;;
print_string "Please enter your name: ";;
let name = read_line ();;
greet name;;
greet_nicely name;;
greet_rudely name
Day 9
Modules
Wouldn't it be nice if we could put all of those related functions in a module, to separate them out from other code?
code:
module Greetings =
struct
let greet_nicely name =
print_endline ("Hello, " ^ name ^ "!");;
let greet_rudely name =
print_endline ("Bugger off " ^ name ^ "...");;
let greet = greet_nicely
end;;
print_string "Please enter your name: ";;
let name = read_line ();;
Greetings.greet name;;
Greetings.greet_nicely name;;
Greetings.greet_rudely name
But, now why don't we put it in a separate file, so any program can use it?
Well, create a new file called "greetings.ml". The name of the file should match the name of the module it will contain, but begin with a lowercase letter. In it we'll put the following code.
code:
module Greetings =
struct
let greet_nicely name =
print_endline ("Hello, " ^ name ^ "!");;
let greet_rudely name =
print_endline ("Bugger off " ^ name ^ "...");;
let greet = greet_nicely
end
Now, our main program:
code:
open Greetings;;
print_string "Please enter your name: ";;
let name = read_line ();;
Greetings.greet name;;
Greetings.greet_nicely name;;
Greetings.greet_rudely name
The "open ..." expression instructs O'Caml to make a module available to us for use.
And to compile, we must instruct the compiler to compile the module as well.
code:
ocamlc greetings.ml helloworld.ml -o helloworld
And one last module trick for today. Let's say that appending "Greetings" to everything gets tedious. We could "include Greetings" which would bring all of Greetings into the scope of our program, or...
code:
module G = Greetings
Or we can even handle this locally.
code:
let module G = Greetings in
G.greet name;
G.greet_nicely name;
G.greet_rudely name;;
Day 10
Let's create a linked list
Untested
code:
type Node :
record
next : ^Node
has_next : boolean
data : int
end record
function new_node (data : int) : ^Node
var n : ^Node
new Node, n
n -> data := data
n -> has_next := false
result n
end new_node
procedure add_to_list (root : ^Node, data : int)
if root -> has_next then
add_to_list (root -> next, data)
else
root -> has_next := true
root -> next := new_node (data)
end if
end add_to_list
The above uses pointer semantics because pointers do not need to have a value assigned with them, so the recursive nature of the type doesn't cause Turing to fill the whole of the known universe with copies. The boolean field allows me to know when there is a next value.
Now, let's take the tiniest of steps in O'Caml and define the type...
Variant types
code:
# type node = Node of int * node | Empty;;
type node = Node of int * node | Empty
#
We now have a type "node" with two constructors. One of those takes a single argument composed of a tuple of an integer and another node, and the other is a "unary constructor". It takes no arguments.
We need a function to determine if the node has a next value. We'll use pattern matching.
code:
# let has_next = function
Empty | Node (_, Empty) -> false
| _ -> true;;
val has_next : node -> bool = <fun>
#
Here the underscore is saying, "I don't really care what ata is stored in the node, so don't bother giving it a name."
In general it's a function which takes one argument and matches that against three possibilities. Either the node will be entirely Empty, the node will have data, but the next node will be Empty, or it will have data and have a next node. That last case is obvious when the others are ruled out, so it needn't be named.
Recursion
I used recursion in the Turing example to add a new node to the list. The diference in O'Caml will be that I cannot mutate the existing list, and must instead create a new one. O'Caml is exceptionally efficient, so this is not a performance issue.
code:
# let rec add_to_list root_node new_data =
match root_node with
Empty ->
Node (new_data, Empty)
| Node (current_data, Empty) ->
Node (current_data, Node (new_data, Empty))
| Node (current_data, next_node) ->
Node (current_data, add_to_list next_node new_data);;
val add_to_list : node -> int -> node = <fun>
#
The "rec" signifies that this will be a recursive function.
Again, we handle three situations. This time each has a unique result.
For adding data to an empty list, we simply get a node with a single value. Adding a value to a list with a value, but no next node is handled by creating a new list with the data in the current list, plus a next node with the new data.
The recursion happens in the event of the last case, where the next node is something other than Empty. In this event we create a new node with the current data, and the new next node is the result of applying the function to the current next node.
Perhaps it's easiest to see it worked out for an example.
Of course, that function only really needs one terminating condition.
code:
# let rec add_to_list root_node new_data =
match root_node with
Empty ->
Node (new_data, Empty)
| Node (current_data, next_node) ->
Node (current_data, add_to_list next_node new_data);;
val add_to_list : node -> int -> node = <fun>
#
Generics
Now, one last trick for the day. In Turing I need to recode the list type for every type of data I want to store in such a thing. I could do that in O'Caml, but why not just have the node type use a generic type?
code:
# type 'a node = Node of 'a * 'a node | Empty;;
type 'a node = Node of 'a * 'a node | Empty
# Node (4.5, Empty);;
- : float node = Node (4.5, Empty)
# Node ("Hello", Empty);;
- : string node = Node ("Hello", Empty)
#
The functions we defined earlier can be used without any changes save their inferred types.
Day 11
A more generic recursive function
There are any number of functions we could write to work on our linked list type. Most will be recursive. Let's look at finding the length of a list.
code:
# let rec length =
function
Empty -> 0
| Node (_, next_node) -> 1 + length next_node;;
val length : 'a node -> int = <fun>
# length (Node (42, Node (73, Empty)));;
- : int = 2
#
Now, let's rewrite this function to use an accumulator.
code:
# let rec length acc n =
match n with
Empty -> acc
| Node (_, next_node) -> length (acc + 1) next_node;;
val length : int -> 'a node -> int = <fun>
#
But why do this? Well, now each recursive call of the function can go on entirely without sending any information back to the calling function. This permits the compiler to heavily optimize the function.
Let's take a closer look at what "length" actually does. It loops over a list. If the list is empty it simply returns some set value it is given. If the list is not empty it applies some function to the value it's given and the current value in the list, though that value is ignored. It uses the result of this function application as the next value for the call to length and passes the remainder of the list as the list to work on in the next call.
All we need to do to abstract this further is to be able to specify which function is applied.
code:
# let rec fold_list fnc initial_value lst =
match lst with
Empty -> initial_value
| Node (value, next_node) ->
fold_list fnc (fnc initial_value value) next_node;;
val fold_list : ('a -> 'b -> 'a) -> 'a -> 'b node -> 'a = <fun>
#
Whoa! What's that inferred type signature mean?
code:
('a -> 'b -> 'a) -> 'a -> 'b node -> 'a
Two types are involved, 'a and 'b. These are generic types. They can be anything.
The fold_list function takes three arguments:
code:
('a -> 'b -> 'a)
code:
'a
and:
code:
'b node
That first argument has more arrows. Don't be too confused. That argument is itself a function. Yes, functions can be passed as arguments to other functions. This function is one which takes two arguments of types 'a and 'b and returns type 'a.
The second two arguments are easy. The return value is fairly easy as well.
Maybe it's easier to understand if we actually use it.
code:
# let add_int_to_float x y =
x +. float_of_int y;;
val add_int_to_float : float -> int -> float = <fun>
#
If we consider this in terms of the first argument to our fold_list function, 'a would be float, and 'b would be int.
Obviously our 'b node will be an int node and the initial value passed to fold_list will be a float value.
Having to name a function for this purpose is inconvenient.
code:
# fold_list (fun a b -> a +. float_of_int b) 0. (Node (4, Node (78, Empty)));;
- : float = 82.
#
A few uses
One sum function:
code:
# let rec sum n =
match n with
Empty -> 0
| Node (v, next_node) -> v + sum next_node;;
val sum : int node -> int = <fun>
# sum (Node (4, Node (5, Empty)));;
- : int = 9
#
Or...
code:
# let sum n = fold_list (fun a b -> a + b) 0 n;;
val sum : int node -> int = <fun>
# sum (Node (4, Node (5, Empty)));;
- : int = 9
# let sum n = fold_list (+) 0 n;;
val sum : int node -> int = <fun>
# sum (Node (4, Node (5, Empty)));;
- : int = 9
#
A function to determine if a list contains a given value.
code:
# let has_value v lst =
fold_list (fun a b -> if b = v then true else a) false lst;;
val has_value : 'a -> 'a node -> bool = <fun>
# has_value 45 (Node (42, Node (56, Node (45, Node (72, Empty)))));;
- : bool = true
# has_value 45 (Node (42, Node (56, Node (47, Node (72, Empty)))));;
- : bool = false
#
What if we want a nice way to print a list?
code:
# let string_of_node n str_func =
"[" ^ fold_list (fun a b -> let s = str_func b in
if a = "" then s
else a ^ ", " ^ s)
"" n ^ "]";;
val string_of_node : 'a node -> ('a -> string) -> string = <fun>
# string_of_node (Node (5, Node (6, Empty))) string_of_int;;
- : string = "[5, 6]"
# string_of_node (Node (5., Node (6., Node (7., Empty)))) string_of_float;;
- : string = "[5., 6., 7.]"
#
In summary
Were this Turing, if we wanted to write a length function, or a function to stringify a list, or a has_value function... those functions would all have to repeat the boilerplate of the looping. In O'Caml through the use of functions that can be passed around and anonymously created we can easily abstract away the tedious pieces of logic.
I'm sure this has been quite a mind trip. Take time to let it sink in.
Day 12
Real variables
I mentioned early in this tutorial that O'Caml bindings are just the binding of a value to a name. They cannot be changed.
This is true. However, it is possible to create variables in the Turing sense.
code:
# let foo = ref 42;;
val foo : int ref = {contents = 42}
# foo.contents;;
- : int = 42
# !foo;;
- : int = 42
# foo := 65;;
- : unit = ()
# !foo;;
- : int = 65
# foo.contents <- 76;;
- : unit = ()
# !foo;;
- : int = 76
#
Now, I'm also going to introduce O'Caml's lists. They're conceptually the same as the one I showed you, but the syntax is a bit nicer. Oh, and a few types of procedural loops too.
code:
# let numbers = [1; 2; 3]
and sum = ref 0
in
for i = 0 to List.length numbers - 1 do
sum := !sum + List.nth numbers i
done;
!sum;;
- : int = 6
# let numbers = [6; 5; 19]
and sum = ref 0
and counter = ref 0
in
while !counter < List.length numbers do
sum := !sum + List.nth numbers !counter;
counter := !counter + 1
done;
!sum;;
- : int = 30
#
Day 13
Object-oriented programming
Let's take a code-heavy approach. A very simple class.
Turing:
code:
class Foo
end Foo
O'Caml:
code:
# class foo =
object
end;;
class foo : object end
#
Let's create a new object.
Turing:
code:
var bar : pointer to Foo
new Foo, bar
O'Caml:
code:
# let bar = new foo;;
val bar : foo = <obj>
#
Let's add a method.
code:
class Foo
export name
function name : string
result "foo"
end name
end Foo
var bar : pointer to Foo
new Foo, bar
var name : string := bar -> name
put name
o'Caml:
code:
# class foo =
object
method name = "foo"
end;;
class foo : object method name : string end
# let bar = new foo in
print_endline bar#name;;
foo
- : unit = ()
#
Note that the O'Caml method is statically-typed, but that the type has been inferred.
Now, let's put some information into the class.
Turing:
code:
class Foo
export initialize, get_name
var name : string
procedure initialize (name_ : string)
name := name_
end initialize
function get_name : string
result name
end get_name
end Foo
var bar : Foo
new Foo, bar
bar -> initialize ("baz")
var name : string := bar -> get_name
puts name
This is pretty straightforward. Our class now contains a variable. Via the initialize procedure we can give that
variable a value. We can get that value with the get_name function. We cannot adirectly change "name" because it
is not exported.
Let's see it in O'Caml:
code:
# class foo name =
object
method get_name : string = name
end;;
class foo : string -> object method get_name : string end
# let bar = new foo "baz" in
let name = bar#get_name in
print_endline name;;
baz
- : unit = ()
#
In the O'Caml code there is no variable. We simply construct the class using an argument "name" which get_name
returns. We could do something classer to the Turing example.
code:
# class foo name =
object
val mutable name_ = name
method get_name : string = name_
end;;
class foo :
string -> object val mutable name_ : string method get_name : string end
# let bar = new foo "baz" in
let name = bar#get_name in
print_endline name;;
baz
- : unit = ()
#
But that's really quite pointless unless we alter the value of name at some point.
code:
# class foo name =
object
val mutable name_ = name
method get_name : string = name_
method set_name new_name = name_ <- new_name
end;;
class foo :
string ->
object
val mutable name_ : string
method get_name : string
method set_name : string -> unit
end
# let bar = new foo "baz" in
let original_name = bar#get_name in
bar#set_name "wooble";
let new_name = bar#get_name in
print_endline original_name;
print_endline new_name;;
baz
wooble
- : unit = ()
#
Interfaces
Now, let's magine a function which takes a Foo object as an argument.
code:
function blah (f : pointer to Foo) : string
result f -> get_name
end blah
Pretty straightforward. But, what if I want blah to accept any object that responds to the get_name method?
Well, then I need that class to inherit from a class with a deferred function get_name.
code:
class HasName
export get_name
deferred function get_name : string
end HasName
class Foo
inherit HasName
body function get_name : string
result "bar"
end get_name
end Foo
function blah (f : pointer to HasName) : string
result f -> get_name
end blah
var a : pointer to HasName
new Foo, a
put blah (a)
O'Caml takes a bit of a different approach. We've seen a lot of type inferencing so far. Let's see it again.
code:
# class foo name =
object
val mutable name_ = name
method get_name : string = name_
method set_name new_name = name_ <- new_name
end;;
class foo :
string ->
object
val mutable name_ : string
method get_name : string
method set_name : string -> unit
end
# let blah f : string =
f#get_name;;
val blah : < get_name : string; .. > -> string = <fun>
#
Here the type of the object passed in as an argument is inferred to be any object with at least a method get_name
that returns string. The ".." indicates it may also contain other methods. Only if the object has this method can
the function work, so the compiler figures out that that is what the type must be.
Of course, we have to tell the function that it returns a string, because the results of get_name are never used in
any way which indicates it must be a string.
Day 14
Recap
It's time for a breather. A chance to look back at what's been touched on thus far.
Day 1
Getting O'Caml and starting the interactive toplevel environment.
Day 2
Values, toplevel feedback and simple operations.
Day 3
Binding names to values.
Day 4
Defining simple functions and a glimpse of a few we can use from the library.
Day 5
Local name bindings.
Day 6
Records, functions with multiple expressions and a bit of pattern matching.
Day 7
Conditionals - we saw that they can return values as well as perform actions.
Day 8
Writing a program, rather than evaluating single expressions in the toplevel.
Day 9
Modules both inline in a source file and as a separate file.
Day 10
Variant types, recursion and generics.
Day 11
Getting more generic with recursion using a folding technique. This involved passing a function to another function as an argument. To accomodate this we saw the introduction of anonymous functions.
Day 12
Mutable values, O'Caml's own list type and procedural loops.
Day 13
Object-oriented programming - basic classes, interfaces and type inferencing.
Sponsor Sponsor
Saad
Posted: Sun Jul 06, 2008 6:04 pm Post subject: Re: [Tutorial] From Turing to O'Caml - Days 1 through 7
Tutorial looks good, and is great for anyone looking for a new language after Turing. I can see wtd but great time and effort into this. I'll give some suggestions on IRC but other then that great job .
rizzix
Posted: Mon Jul 07, 2008 1:26 am Post subject: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 4
Nice work wtd!
michaelp
Posted: Mon Jul 07, 2008 3:52 pm Post subject: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 4
Days 1 through 7 now... great work wtd!
wtd
Posted: Sat Jul 12, 2008 3:11 pm Post subject: Re: [Tutorial] From Turing to O'Caml - Days 1 through 8
Bump for edits.
michaelp
Posted: Sat Jul 12, 2008 3:31 pm Post subject: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 9
Day 8 is repeated.
rdrake
Posted: Sat Jul 12, 2008 3:57 pm Post subject: Re: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 9
michaelp @ Sat Jul 12, 2008 3:31 pm wrote:
Day 8 is repeated.
Little typo, fixed now. Any O'Caml-related questions?
michaelp
Posted: Sat Jul 12, 2008 4:07 pm Post subject: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 9
No, I'm good. (for now!)
I have a cast on (full arm) so I can't really do much programming ATM.
Sponsor Sponsor
wtd
Posted: Sat Jul 12, 2008 6:58 pm Post subject: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 9
But I did it just for you! With all of the type-inferencing, there's less to type.
Saad
Posted: Sat Jul 12, 2008 7:17 pm Post subject: Re: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 9
wtd @ Sat Jul 12, 2008 6:58 pm wrote:
But I did it just for you! With all of the type-inferencing, there's less to type.
Offtopic but, I couldn't help but laugh out loud reading that. Nicely said
wtd
Posted: Thu Jul 17, 2008 4:30 am Post subject: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 11
Day 11 has been added. It is quite a bit to digest for the uninitiated. Take it slow and ask questions.
Zampano
Posted: Thu Jul 17, 2008 6:06 am Post subject: Re: [Tutorial] From Turing to O'Caml - Days 1 through 11
That's very thorough.
michaelp
Posted: Thu Jul 17, 2008 9:01 am Post subject: Re: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 11
wtd @ Thu Jul 17, 2008 4:30 am wrote:
Day 11 has been added. It is quite a bit to digest for the uninitiated. Take it slow and ask questions.
Aw, for me? You shouldn't have!
wtd
Posted: Sat Jul 19, 2008 5:08 pm Post subject: RE:[Tutorial] From Turing to O\'Caml - Days 1 through 11
Uninitiated when it comes to functional programming.
wtd
Posted: Sat Jul 26, 2008 12:45 am Post subject: Re: [Tutorial] From Turing to O'Caml - Days 1 through 14
The recap for days 1 through 13 has been posted. So, any questions?