CyberSpy

Rantings from a guy with way too much free time

OCaml Hello World and more

2018-02-06 programming Rob Baruch

Well Hello There!

As tradition has it, every new programming language experience must begin with the basic Hello World. Let’s walk the the basics of how to set up our development environment specifically for OCaml and demonstrate how to compile our basic program.

Warming up our Environment

Of course we could just use our vanilla editor to enter our OCaml programs, but a more efficient work environment leverages an extensible editor that is aware of our programming language. Editors like sublime, Atom, and Visual Studio Code are some of the more popular selections. Vim and Emacs are also options, but the new tools tend to support vi/emacs style editing while offering more extensive and expansive features.

In this series, I will use Visual Studio Code. And here are some tools that you might want to add to make your experience more enjoyable when writing OCaml code.

OCaml for VS Code

Install the OCaml and Reason IDE extension and reload after the installation completes. This extension will:

  • highlighting
  • editing
  • navigation
  • static analysis

Before you install this extension be certain to install the opam package merlin. Once installed, you have a convenient IDE setup for writing OCaml with VS Code. Cool, now with the editor installed and configured, let’s fire it up and save our first OCaml file, hello.ml.

let main ()  =
        output_string stdout "Hello world!\n";;

main ()  ;;

We can compile our simple Hello World! program using the OCaml compiler, ocamlc. To compile:

$ ocamlc -c hello.ml
$ ocamlc -o hello hello.cmo

$ ./hello
Hello world!

Ta Da! That’s it! So, *Hello World isn’t so exciting, let’s move beyond this trivial example to some more insightful demonstrations of language features in OCaml.

Basics Beyond Hello World!

Now that we’re all setup to write and compile OCaml programs, let’s enumerate through some basic stuff to expand your knowledge of the language features available to us programmers. If you’re already a polyglot, a great source of “How to x in language y” is available at RosettaCode. Take a look for example at how we might solve the Sirpinski Carpet. We can take a look at a traditional solution in Golang. And then, understanding the algorithmic implementation, we can see how we might solve the same probmelm in OCaml.

Here’s the Golang implementation:

package main
 
import (
    "fmt"
    "strings"
    "unicode/utf8"
)
 
var order = 3
var grain = "#"
 
func main() {
    carpet := []string{grain}
    for ; order > 0; order-- {
        // repeat expression allows for multiple character
        // grain and for multi-byte UTF-8 characters.
        hole := strings.Repeat(" ", utf8.RuneCountInString(carpet[0]))
        middle := make([]string, len(carpet))
        for i, s := range carpet {
            middle[i] = s + hole + s
            carpet[i] = strings.Repeat(s, 3)
        }
        carpet = append(append(carpet, middle...), carpet...)
    }
    for _, r := range carpet {
        fmt.Println(r)
    }
}

And now here’s a crack at how you’d write the solution in OCaml

let rec in_carpet x y =
  if x = 0 || y = 0 then true
  else if x mod 3 = 1 && y mod 3 = 1 then false
  else in_carpet (x / 3) (y / 3)
 
(* I define my own operator for integer exponentiation *)
let rec (^:) a b =
  if b = 0 then 1
  else if b mod 2 = 0 then let x = a ^: (b / 2) in x * x
  else a * (a ^: (b - 1))
 
let carpet n =
  for i = 0 to (3 ^: n) - 1 do
    for j = 0 to (3 ^: n) - 1 do
      print_char (if in_carpet i j then '*' else ' ')
    done;
    print_newline ()
  done;;

  carpet 3;;

The first thing to notice is that both implementations focus on using its respective language features to solve the problem. Golang, not being an inherently recursive language implements the (naturally recursive problem statement) iteratively. OCaml, with strong functional orientation, solves the problem using recursion. Moreover, notice the reserved keyword, rec, preceeding let. This keyword explicitly tells OCaml that we are defining a recursive function. More on that later.

What’s in the (OCaml) Box - A survey of language features.

For those that prefer examining specific language elements one at a time rather than infer meaning from other programs, Below is a review of what elements comprise the basics of OCaml.

Basic Assignment

Simple assignments. Here we can see assignments can be singular, or multi-valued. Note the response from assignment. The interpreter responds with the val type of the expression. OCaml is a strongly-typed language. As such, the interpreter will tell you what the type is based on the value of the expression.

# let a = 1 ;;
val a : int = 1

# let a,b = 1,2.;;
val a : int = 1
val b : float = 2.

Beyond simple data-types of ints, floats, and strings, we can have more complex types like Lists. Here we assign l to the list expression containing strings. Lists are homogeneous - they must contain all of the same type, be that primitive or user-defined.

# let l  = [ "a"; "list"; "of"; "words"]
val l : string list = ["a"; "list"; "of"; "words"]

For example, we can define a type, dog that is a record of an int representing age, and string representing name.

# type dog = { age : int; name : string; }
type dog = { age : int; name : string; }

# let pound = []
  let pound = {age = 2; name = "fluffy"} :: pound
  let pound = {age=3; name = "penny"} :: pound
  let pound = {age=5; name="max"} :: pound

val pound : dog list =
  [{age = 5; name = "max"}; {age = 3; name = "penny"};
   {age = 2; name = "fluffy"}]

Another point of caution. We delcared an empty list and then used the list-concatenation operator to add elements to our list. If we inspect the length of our list, List.length pound, we see it’s of length $3$. Notice what happens if we construct a list as follows:

# let pound = [{age = 2; name = "fluffy"}, {age=3; name = "penny"}, {age=5; name="max"}];;

val pound : (dog * dog * dog) list =
  [({age = 2; name = "fluffy"}, {age = 3; name = "penny"},
    {age = 5; name = "max"})]

Notice the different types each assignment creates. The first one creates a dog list typed collection, while the second one creates a three-tuple-dog list containing one element. The short story is that you need to be careful when creating lists verses tuples. Here’s another way to use the :: operator to do multiple concatenation all in one assignment.

let pound = []
let pound = {age = 2; name = "fluffy"} :: {age=3; name = "penny"}  ::{age=5; name="max"} :: pound;;

More about strict types

So clearly OCaml is going to play rough with types. This behavior is no less stringent with integers and floats in numerical expressions. Take a simple function definition to sum two numbers (more on function definitions later).

# let sum a b = a + b;;
val sum : int -> int -> int = <fun>

Notice here the value of sum: it’s explicitly typing our function to add solely integers. If we tried to add an integer and a float, look what happens:

 # sum 2  3. ;;
 Error: This expression has type float but an expression was expected of type
         int

Fortunately, type-casting is available to explicitly align our types:

# sum 2 (int_of_float 3.) ;;
- : int = 5

Going further, every expression is strongly typed. Watch what happens when we define a new function using our sum function.

# let sum' = sum 5 ;;
val sum' : int -> int = <fun>

Here we define sum' (notice the ' is a valid character in a variable name) as the sum function and a $5$. And it tells us that this is a new function!! We didn’t use the function keyword, so how is this a function? Well, OCaml knows that this is a function and the second argument to sum is an int but it’s missing. So sum' is a function that now takes only one int as the $5$ is the first argument to sum in the expression. Watch what happens when we call our new function.

# sum' 6;;
- : int = 11

# let s = sum' 5 + sum 3 4 ;;
val s : int = 17

Functional or Imperative – Your Choice!

Before we finish this post, let’s look at some imperative programming in OCaml. Let’s use the built-in Array type and create an array of integers. This is our traditional mathematical vector object. We can create a function that computes the point-wise product of two vectors easily using loops:

let (^*) a b =
let len = min (Array.length a) (Array.length b) in
let prod = Array.make len 0.0 in
for i = 0 to len - 1 do
    prod.(i) <- a.(i) *. b.(i)
done;
prod;;

So, at first glance, this looks super-confusing. But breaking it down will quickly show that it’s actually pretty easy to read! We create a function that takes two parameters, a, and b. And the name of the two-parameter function is ^. Wait, what?? We can do that? Yup. By wrapping the two characters with the parentheses, we name our new function as such. Moreover, we can use it as an infix function as it’s now a newly defined operator. Next we see we use the built-in Array object to get the length of the two parameters and construct a new array, prod that is of size as big as the smallest vector. We didn’t type our parameters, so how does it know that we are dealing with arrays? Since we use it as a parameter to Array.length our function operator infers that we mean for the parameters to be of type Array. And in fact more than just type Array, but Array float as wee use the float multiply operator rather than the int multiply operator (** *. ** verses ** * **, respectively).

And now we use the imperative for-loop construct to loop over the vector elements, multiply them together (using the float-multiply operator) and assign them to our prod. We return the value of the prod as the expression of our function.

# let a = Array.make 5 2;;
  let b = Array.make 5 2.;;

  let c = a ^* b;;
val c : float array = [|4.; 4.; 4.; 4.; 4.|]

Next up, more on functions…

comments powered by Disqus