Monthly Archive for March, 2011

Boo.

It was early february a few years ago. My girlfriend had just left France, and was to stay in China for six months, so I was getting back to my old teen programmer habits of writing code alone at my desktop computer at 4:00 am. My room was the typical no-girlfriend-around mess, with old clothes lying on the bed, dishes on the corner of the desk, unused laptops scattered around the room, soft music in my headphones and my two monitors being the only light source in the room.

I’m afraid I don’t really remember in what order things did happen that night. I do remember hearing a short, sad whistle coming from behind me, feeling something soft, large and warm pressed against my lower back, and all the lights suddenly going out, all in quick succession.

I immediately stood up and turned around, my not-yet-wireless headphone cord dragging unidentified objects off the desk into a cacophonous cascade, my badly adjusted eyes trying to see through the darkness to understand what had just touched me, or where that noise might have come from.

So I just stood there for a while. After a few seconds, I heard a noise, different from the first one. It sounded like fingernails on plastic. I could not precisely locate where it was coming from, and it was so soft that I quickly dismissed it as either a by-product of my terrified imagination or one of the fallen objects rolling around.

Then, I heard that same noise again ten seconds later. And again every ten seconds after that. It did not sound like anything a machine could make (and besides, all appliances and computers were powered off anyway), in fact it had an almost human feel to it.

I called out “Il y a quelqu’un?” (“Is anybody there?”), then immediately felt silly at asking such a question out loud while alone at night in my apartment. My question was shortly followed by a short male laughter coming from somewhere in my room. Then the finger-on-plastic noise again. And again.

It occurred to me that having a panic attack in the dark was not very advisable, and that I could at least use one of my laptops as a light source. I found one, picked it up, placed it on the desk and opened the lid. The screen came to life showing a browser pop-up window for some online poker playroom (don’t judge me!) that bathed the room in a pale green light. I started looking around for my flashlight, which would provide better illumination and let me reach the fuse box, when I heard someone distinctly laughing right behind me.

I turned around, striving to drive my elbow into what I hoped to be a sensitive part of the intruder’s anatomy and ended up being a lot of otherwise uninhabited air. And I notice a video playing on the poker pop-up, of a smug man like you expect to see on a poker pop-up, juggling with some poker chips in his hand and making that fingernail-on-plastic noise. And laughing every ten animation loops. Genius.

From what I gathered the next day, the power had been cut in the middle of the night, prompting my alarm clock to whistle loudly in its death throes. The laptop had been playing that poker video since I laid it down, but the sound had been drowned out by the music in my headphones and only became audible when the music stopped and I took them off. I still have no idea what the warm sensation was.

Also, you might want to read this [reddit].

Fukushima – Remind Me Later

The nuclear power plant Fukushima I has been the center of international attention this week in the aftermath of the Sendai earthquake and tsunami. They are currently having technical issues in four distinct reactors, for a variety of reasons. Depending on whom you ask, this might result in either putting those reactors out of commission permanently, or inflicting potentially lethal radiation doses throughout in East Asia.

Nuclear science is hard, but I still kind-of-get-it because I have a reasonable background in physics. But a real-life nuclear reactor involves a huge amount of small practical details that are beyond the grasp of anyone but a trained engineer — and even then, different types of reactors involve different areas of expertise. I suspect that most experts in the specific technology used at Fukushima I are busy solving the hell out of that mess, so we will not be getting a detailed explanation from them any time soon.

Until then, our main sources of information are far less reliable. Popular french media website Rue89 ran a headline about a nuclear explosion having happened, which was later revealed to be incorrect. Contradicting sources indicate the level of irradiation of workers anywhere between 100% and 2000% of allowed the yearly dose, some state that the confinement was breached while others state that it was not, and the French and Japanese atomic agencies disagree on whether this is a 4/7 or 6/7 catastrophe. Anti-nuclear activists are quoted in the media and rant on about «huge risks» without ever mentioning what those risks are and why they are huge, while pro-nuclear activists insist that sustained damage is well within acceptable (and foreseen) limits. And the media vomits technically incorrect approximations to make things easier to understand, skipping such critical details as the half-life of radioactive gases or the impossibility for the fuel to turn into an atomic bomb.

I’ve had the mixed pleasure of listening to an interview during which the first explosion at the Fukushima plant happened — the journalist interrupted the interview and asked the interviewee, a nuclear power expert, whether the explosion that just happened was a very bad thing or not. I know experts are known for being above-average geniuses, but please! How can you expect anyone to provide a reasonable answer based on a single-sentence description of an unconfirmed explosion?

I trust that the experts out there in Japan know what they are doing, and are taking the appropriate steps to protect the population should the worst scenario happen. So, I’m going to patiently wait until the crisis is over and all those experts can work on a detailed postmortem I can read through.

It’s Payback Time

I have another problem for you. The European Union has banknotes of values 500€, 200€, 100€, 50€, 20€, 10€ and 5€, and coins of values 2€, 1€, 0.5€, 0.2€, 0.1€, 0.05€, 0.02€ and 0.01€. As you might expect, when you need to pay a certain amount (say, 118€), you give the cashier a few bills and coins (1×100€ + 1×20€) and you get some change back (1×2€). For every amount, there exists a perfect transaction which minimizes the number of notes and coins that are exchanged. For 118€, that number is 3 (as shown above). For 5€, it’s 1 (give a single banknote). For 9€, it’s 2 (give a 10€, get 1€ back).

Here’s the question: what is the amount, smaller than 500€, which causes the highest number of notes and coins to be exchanged?

While this could be solved mathematically, I am going to go for a brute-force algorithm because it’s easier on the mind. The approach consists in exploring all possible transactions of size one (only one note or coin is exchanged) and remember what amounts were available, then construct all transactions of size two (by adding a coin or note to a transaction of size one), and so on until all new transactions end up being for amounts that have already been explored (there are 50001 possible amounts, so the algorithm will inevitably terminate, even if it takes a while).

First, I need a representation of all available coins and notes (in both positive and negative form, because in a transaction they can go either way). I will save some typing by writing some code that generates them:

let values =
  List.concat
    (List.map
       (fun v -> [ 5*v ; 2*v ; v ; -v ; -2*v ; -5*v ])
       [ 100_00 ; 10_00 ; 1_00 ; 10 ; 1 ])

This code turns every power of 10 between 10000 (100€) and 1 (0.01€) into a six-item list (for instance, 10 becomes 50, 20, 10, -10, -20, -50), then concatenates all these lists together. The resulting 30-element list contains all 15 notes and coins twice: once positive and once negative.

To represent a transaction (a node in my exploration algorithm) I will define a brand new type that contains the current total in the transaction, the change (a list of notes and coins), and the transaction size (this is actually equal to the length of the change list, but storing it is faster than having to compute it from scratch). The type looks like this:

type t = { total : int ; change : int list ; count : int }

Before I continue, a quick word about complexity: in my algorithm, I expect to do a lot of “has this node already been explored” queries, because this is what determines whether I need to stop processing. I also expect the list of explored nodes to grow to a size of 50001, and if I have to run such a query for every single node, I am looking at a minimum of one billion comparisons. This is starting to get a bit too large for my tastes (probably several minutes worth of computations), so I will look into an alternative storage method: sets.

Sets are optimized for quick “add this element to the set” and “is this element in the set?” operations, running in logarithmic time rather than linear time. This makes the above queries several orders of magnitude faster (expect a 50000-element set to behave about as fast as a 50-element list). In Objective Caml, sets are available as part of the standard library, and can be used by defining a module containing the type you wish to store in the set, then call Set.Make to create a specialized set module dedicated to your type:

module Node = struct
  type t = { total : int ; change : int list ; count : int }
  let compare node_1 node_2 = compare node_1.total node_2.total
end

module NodeSet = Set.Make(Node)

Nothing very complex here: my module defines a type named t (as expected by the set module) and a compare function that relies on the built-in compare function to compare the totals (because my actual query is “was a node with this total explored?”) so that nodes with equal totals but different transactions are still considered the same.

While I’m doing this, I will also add two other functions to my node module: a function which adds a coin to a node (this changes the total, prepends the coin to the transaction list, and increments the count), and a function which returns whether the node is valid (its total is between 0€ and 500€). I will also add an empty node from which the exploration will start:

module Node = struct

  type t = { total : int ; change : int list ; count : int }

  let compare node_1 node_2 = compare node_1.total node_2.total

  let add node coin = {
    total  = node.total + coin ;
    change = coin :: node.change ;
    count  = node.count + 1
  }

  let is_valid node = node.total >= 0 && node.total <= 500_00

  let empty = { total = 0 ; change = [] ; count = 0 }

end

Next, I use the set module to define a “node is not explored yet” function:

let is_unknown known node = not (NodeSet.mem node known)

Here NodeSet.mem element set is a function which returns true if the element is inside the set (“mem” means “member”). Based on this function, I can write the “explore one node” routine, which consists in adding all possible coin and note values to the node, eliminating any nodes that end up outside the 0€ – 500€ range and any nodes that have already been explored, and then returning those nodes. Elimination will use the built-in List.filter predicate list function, which keeps only the elements in the list for which the predicate function returns true. My predicates are “is not explored” and “is valid”:

let explore known node =
  let reached    = List.map (Node.add node) values in
  let valid      = List.filter Node.is_valid reached in
  let unexplored = List.filter (is_unknown known) valid in
  unexplored

The exploration process is fairly similar to what happened in the previous installment. An unexplored list is kept, and one item is taken from it and explored, which creates new unexplored items. Any items added to the list of unexplored items is also added to the set of known items to avoid duplicates. When the unexplored list runs out (everything has been explored), the set of known items is returned, because it contains all reachable nodes including the largest transaction. In order to make sure the smallest transaction is kept for every amount, the algorithm processes shorter transactions before longer transactions (so that the first transaction for any given total is always the shortest transaction for that total). This is done by appending newly found unexplored elements at the end of the unexplored list. The complete code is:

let rec process known unexplored =
  match unexplored with
    | [] -> explored
    | next :: rest ->
      let found = explore known next in
      let known = List.fold_left (fun known node -> NodeSet.add node known) known found in
      process known (rest @ found)

This is the hardest function of the bunch, and the most complex line is the one that uses List.fold_left to add several new nodes to the set of known nodes. As an interesting side note, the order of the parameters to NodeSet.add is not adapted to the List.fold_left function, which forces me to write an anonymous function that swaps the two arguments it receives.

Once this is written, it’s a simple matter of calling the function:

let explored = process NodeSet.empty [Node.empty]

And extracting the maximum using NodeSet.fold, the set equivalent of List.fold_left:

let maximum =
  NodeSet.fold
    (fun node best -> if node.Node.count > best.Node.count then node else best)
    explored Node.empty

Since the set is sorted in ascending order of node totals, I know that if several transactions have the maximum size, the one with the smallest total amount will be found first and kept as the best. And the result is 333.33€, for which the optimal transaction consists in giving 500€ and receiving 100€, 50€, 10€, 5€, 1€, 0.5€, 0.1€, 0.05€ and 0.02€ back (a total of ten coins and banknotes). The computation, when compiled as an optimized executable, takes 58 seconds on a computer that was average by early 2008 standards.

Initial Sequence

Let’s play another little game. I’ll give you a few integers, for instance 225, 318, 529 and 713. I need you to compute all numbers that can be computed by adding those integers together any number of times (for instance, 450 = 225 + 255, 543 = 225 + 318 …). Of course, there’s an infinite number of them, so I just want you to be able to provide me with, say, the 1000 smallest ones.

How can one solve this? There’s no simple formula for determining what number comes next, so it boils down to computing enough numbers to be certain that the 1000 smallest values have all been computed, and then sort the result to extract the first 1000.

Finding these numbers looks a lot like exploring a map or maze. From every number, there are four paths one can follow, which consist in adding one of the four initial integers to the current number. So, the paths from 225 lead to 450, 543, 754 and 938. Exploring a number means to write down all four destinations on the map, to be explored later. By definition, all paths lead to higher numbers, which provides a helpful property: if the 1000 smallest integers on the map are all explored, then no path from an unexplored integer can lead to a number smaller than one of those already explored (because that unexplored integer is greater than those 1000 explored ones, and so the path would lead to an even greater integer).

So, the algorithm consists in keeping a list of unexplored numbers and then repeating the following process a thousand times: pick the smallest integer, explore it (adding new numbers to the list of unexplored numbers) and add it to the list of explored items. In the end, the explored list contains 1000 items, sorted from least to greatest, with the guarantee that no unexplored integers can lead to smaller ones.

You might recognize this as a variation on Dijkstra’s Algorithm.

Writing this down in Objective Caml requires a preliminary decision: how are we going to extract the smallest number from the unexplored list? One way is to keep the list sorted at all times—this makes extraction easy (pick the first element) but insertion hard (you have to keep the list sorted). Conversely, one may keep the list in random order—this makes insertion easy (just prepend) but extraction hard (you have to find and remove the minimum). Since the latter is marginally harder than the former, I’ll go with the first option. Inserting an element into a sorted list is a textbook example of recursive function:

let rec insert sorted element =
  match sorted with
    | [] -> [element]
    | head :: tail -> if element < head then element :: sorted
                      else if element = head then sorted
                      else head :: insert tail element

The algorithm is fairly straightforward: if the list is empty, return a one-element list containing only the element ; if the first item in the list is greater than the element, then the element goes in first and the rest of the list follows ; if the element is equal to the first item in the list, then return the list as-is to avoid duplicated ; if the element is greater than the first item in the list, then insert recursively that element in the remainder of the list and prepend the original first element to the result.

Then, there’s the exploration function, which is recursive as well, and expects two arguments: the number of items left to explore, and the list of unexplored elements. The list of explored elements is returned. It is written as:

let values = [ 225; 318; 529; 713 ]

let rec explore n = function
  | [] -> []
  | current :: unexplored ->
    if n = 0 then [] else
      let new_values = List.map ((+) current) values in
      current :: explore (n-1) (List.fold_left insert unexplored new_values)

The exploration function extracts the first element of the unexplored list (if that is empty for some reason, it returns no elements, because there’s nothing to explore) and computes new_values by adding all the original values to the explored element. This uses the built-in List.map func list which calls func on every item of the list and places it in a new list that is then returned. Here, the function is (+) current, which is a function that adds “current” to its argument. So, if the explored element was 225, the returned list would contain 450, 543, 754 and 938.

The code then inserts those four values into the unexplored list by using List.fold_left (which I have already covered yesterday), and recursively calls explore (asking for one less element). It then prepends the current element to the returned list (because it’s smaller than all the elements to be explored next) and returns everything.

That’s all! To find the 20 smallest values, one writes:

let smallest_20 = explore 20 values

These are:

[225; 318; 450; 529; 543; 636; 675; 713; 754; 768; 847; 861; 900; 938; 954;
 979; 993; 1031; 1058; 1072]

Any questions? Ask away in the comments section!

Heads and Tails

Let’s play a little game. You have nine coins laid out as a 3×3 square in front of you. Three of them are tails while the other six are heads, in the following shape:

T T H
H T H
H H H

You are allowed to flip coins (heads becomes tails and tails becomes heads) but only in groups of three: all three coins on a line, column or diagonal can be flipped together. So, flipping the top line from the previous example would result in this:

H H T
H T H
H H H

Your objective is to determine whether this configuration can be flipped into an all-heads (or all-tails) configuration, and if it can, provide a possible flip sequence.

I’m going to find the solution using a short program written in Objective Caml. The first thing I’m going to do is translate as much information about the problem into data that the program can manipulate. The nine coins will be numbered from one to nine in reading order:

1 2 3
4 5 6
7 8 9

So, there are eight possible moves (three lines, three columns, two diagonals) and we can represent each as a list of three integers:

let moves = [
  [ 1 ; 2 ; 3 ] ; [ 4 ; 5 ; 6 ] ; [ 7 ; 8 ; 9 ] ;
  [ 1 ; 4 ; 7 ] ; [ 2 ; 5 ; 8 ] ; [ 3 ; 6 ; 9 ] ;
  [ 1 ; 5 ; 9 ] ; [ 3 ; 5 ; 7 ]
]

Each coin will have its state represented by a boolean: true for tails, false for heads. The current state of the board will be represented by key-value pairs, where the first element of the pair is the number of the coin and the second is its state (true, or false). A missing coin is treated as false for conciseness. So, the initial state looks like this:

[ 1, true ; 2, true ; 5, true ]

Be careful: semicolons separate items of a list, while commas separate items of a tuple (such as a key-value pair).

To get the current state of a coin, I can use the built-in Objective Caml function List.assoc key list which returns the value associated to a given key in a list of key-value pairs, and raises a Not_found exception if the key was not found. The function that returns the current state is then simply:

let state board cell =
  try List.assoc cell board with Not_found -> false

If you’re not familiar with Objective Caml notation function for functions, just remember that function arguments are not wrapped in parentheses like many other languages require. Instead, they simply appear after the function name. When you provide a function with all its argument, it is called and replaced by its return value. If you’re confused by the fact that the function above returns no value, remember that Objective Caml does not need a return keyword—it always returns whatever its body evaluates to (in this case, the value returned by List.assoc or, if it raises an exception, false).

I will also need the ability to flip a coin. This is done by prepending the new key-value pair to the existing list. It is not necessary to remove the previous binding of the key, because List.assoc stops as soon as it finds the key, so subsequent occurrences are simply ignored. The code is simply:

let flip board cell =
  (cell , not (state board cell)) :: board

The function simply reads the current value of the cell in the board, binds it to the cell (as a key) and prepends it to the list using item :: list notation. This does not change the value of board: instead, a new list is returned with the appropriate values inside, so that board can be reused elsewhere if necessary. This is a common occurrence in functional programming languages, and is extremely useful because values are guaranteed to be remain the same at all times.

One last thing we need before writing the actual algorithm is a function that determined whether the board has been solved. Since an all-false board can be trivially turned into an all-true board (simply flip all lines), I am just going to check whether all coins are true. This is done by using the List.for_all predicate list built-in, which returns true if and only if predicate returns true when called on every item in the list. The code is:

let is_solution board =
  List.for_all (state board) [ 1 ; 2 ; 3 ; 4 ; 5 ; 6 ; 7 ; 8 ; 9 ]

You might be confused about why state is only given one argument when it expects two. This is known as currying and is another common occurrence in functional programming languages. The basic idea is that if you give a two-argument function its first argument, then it becomes a one-argument function that expects its second argument. In this situation, state board becomes a function which expects a cell as an argument and returns the state (true or false) of that cell within the board. This precisely what we want our predicate to be.

The algorithm itself needs an initial board to work on (the one we are trying to solve) and a list of moves that it can try to solve the board. It then tries every combination of moves until it runs out or finds a solution (it makes no sense to apply a move twice, and the order in which they are applied is irrelevant, so for eight possible moves, there are 28 = 256 combinations: something that can be processed by a modern computer in the blink of an eye).

Actually trying every possible combination might seem a hard thing to do until you notice that it’s an inherently recursive process: if you have eight possible moves, you can try to apply the first one and try solve the resulting board with the other seven possible moves, or not apply the first one and try to solve the initial board with the other seven possible moves. Repeat this process until you have no moves left—at which point, if the board is all-true, you win, otherwise you lose.

let rec solution_exists board moves =
  match moves with
    | [] -> is_solution board
    | head :: tail -> solution_exists board tail ||
                      solution_exists (List.fold_left flip board head) tail

The rec keyword denotes that the function is recursive (which allows it to call itself). It examines the list of possible moves using match, which allows here two different possibilities: either the list is empty, in which case a solution exists if and only if the board is the solution ; or the list contains one element (the head) along with zero or more other elements (the tail, which is itself another list). In the latter case, the function recursively calls itself twice: once while ignoring the value of the current element, and once by applying it. The function returns true if either of the recursive calls return true.

Applying the current move relies on a built-in function List.fold_left func accumulator list which is perhaps one of the most useful functions in Objective Caml. It works by reading items from the list in order, calling func accumulator x for every item in the list and storing that result back into the accumulator, and finally returns the value of the accumulator. Let’s see an example with the first possible move, [1;2;3].

let result = List.fold_left flip board [1;2;3]

(* Is equivalent to: *)

let result =
  let board = flip board 1 in
  let board = flip board 2 in
  let board = flip board 3 in
  board

In short, this applies all flips determined by the current move and returns the resulting board. But, of course, it is much shorter than the version written by hand—this was made possible by the tendency of many functional languages to manipulate lists of values instead of code. List.fold_left is one of the many ways of applying a single piece of code over a list of values and obtaining some results.

Now that all of this is done, let’s look for our solution:

let find = solution_exists [ 1, true ; 2, true ; 5, true ] moves

The result is false, meaning that no solution was found. Let’s try with another board:

T T H
H H H
T H H

Or, in terms of code:

let find = solution_exists [ 1, true ; 2, true ; 7, true ] moves

This returns true, meaning that a solution exists. How do we adapt our code to return a solution instead of just true? First, we need a way to give a name to every move, which we are going to do by associating a bit of text to each in the list of possible moves. This looks like this:

let moves = [
  "L1", [ 1 ; 2 ; 3 ] ; "L2", [ 4 ; 5 ; 6 ] ; "L3", [ 7 ; 8 ; 9 ] ;
  "C1", [ 1 ; 4 ; 7 ] ; "C2", [ 2 ; 5 ; 8 ] ; "C3", [ 3 ; 6 ; 9 ] ;
  "D1", [ 1 ; 5 ; 9 ] ; "D2", [ 3 ; 5 ; 7 ]
] 

This means that the solving function also needs to be rewritten to take this into account. Instead of returning true or false, we are going to use another Objective Caml built-in type which allows one to either return a value, or return nothing—we return the solution if it exists (Some ["C1";"D2"]) and nothing if there was no solution (None). The code becomes this:

let rec solve board moves =
  match moves with
    | [] -> if is_solution board then Some [] else None
    | (name, move) :: tail ->
      match solve (List.fold_left flip board move) tail with
        | Some solution -> Some (name :: solution)
        | None -> solve board tail

This is a bit more complex, but the recursive algorithm is the same: if there are no moves left, then either the board is solved (return a zero-move solution Some []) or it isn’t (return an absence of solution None. If there’s a possible move, remember its name, apply it and check whether a solution was returned: if there was one (Some solution), then just prepend the name of the move to the solution and return it (Some (name::solution)) ; if trying the move did not allow for a solution, then try solving the board without that move and return whatever solution or absence of solution this results in.

Applying this to the original problem returns None as expected, while applying it to the second problem returns Some ["L1";"L2";"C2";"D1"] — a valid solution indeed.

The complete (and quite short!) code is:

let moves = [
  "L1", [ 1 ; 2 ; 3 ] ; "L2", [ 4 ; 5 ; 6 ] ; "L3", [ 7 ; 8 ; 9 ] ;
  "C1", [ 1 ; 4 ; 7 ] ; "C2", [ 2 ; 5 ; 8 ] ; "C3", [ 3 ; 6 ; 9 ] ;
  "D1", [ 1 ; 5 ; 9 ] ; "D2", [ 3 ; 5 ; 7 ]
] ;;

let state board cell =
  try List.assoc cell board with Not_found -> false

let flip board cell =
  (cell , not (state board cell)) :: board

let is_solution board =
  List.for_all (state board) [ 1 ; 2 ; 3 ; 4 ; 5 ; 6 ; 7 ; 8 ; 9 ]

let rec solve board moves =
  match moves with
    | [] -> if is_solution board then Some [] else None
    | (name, move) :: tail ->
      match solve (List.fold_left flip board move) tail with
        | Some solution -> Some (name :: solution)
        | None -> solve board tail

let find = solve [ 1, true ; 2, true ; 7, true ] moves

Any questions? Let me know in the comments!

Do Variant Types in OCaml Suck?

I recently stumbled once more on Tomasz Wegrzanowski’s blog, on one of the early articles he wrote in 2006 about Objective Caml — unequivocally titled Variant Types in OCaml Suck. Hence the title of this article.

Objective Caml polymorphic variants have the benefit of being created on the fly: you just write ` kitten and you have successfully created a value of type [> `kitten] (do note that you have also created a singleton type out of nothing — this can be quite useful in some cases). Of course, the ability to define types on the fly everywhere puts a strain on the type inference algorithm, who must then determine whether you are mistaken or not.

Type Inference : Damas-Milner

Type inference in ML language strives to determine the type of every value and variable in the code. It does so by giving every variable a completely general type, and then examining any constraints that apply to progressively turn it into the most general type that satisfies all constraints. Let’s try a quick example:

  • When you write fun x -> fst x + snd x, the type of x is initially 'x.
  • The type of fst is 'a * 'b -> 'a, which means that the type of x must have the shape 'a * 'b (because it’s provided as an argument to fst). So, the constraint implies that 'x = 'xa * 'xb (and that fst x is of type 'xa).
  • The type of operator + is int -> int -> int, from which the type of fst x is constrained to be 'xa = int and thus 'x = int * 'xb.
  • The type of snd is 'a * 'b -> 'b, but since 'b = int in the present context, this means that its actual type is 'a * int -> int and thus 'x = int * int

So, by applying constraints deduced from the code, the type inference algorithm computes the type of variable x. Applying a type constraint is known as unification: two types are determined to be equal, so they are superimposed on top of one another in order to construct a new unified type. This happened in the last step above: x had been determined to be of type int * 'b and this type was unified with type 'a * int. Unification is a simple pattern-matching exercise: one explores the two types as if they were trees, and…

  • If identical nodes are encountered (such as a _ * _ 2-tuple node or a leaf-less int basic type node), the unification proceeds recursively.
  • If a type variable (such as 'a or 'b) is encountered, it is declared equal to the corresponding element in the other type.
  • If two different nodes are encountered (such as a _ * _ constraint being unified with an int variable), an error happens.

Polymorphic Variant Types

Polymorphic variant types are lists of possible constructors. For instance, [`kitten | `walrus] represents a type that contains the constructors `kitten and `walrus.

When you write `kitten, the resulting type is [> `kitten]: an open variant type that can be unified with other variant types. Consider, for instance, the array [| `kitten ; `walrus |]: it is of type 'a array where 'a can be unified with both [> `kitten] and [> `walrus], which results in [> `kitten | `walrus].

Now, let’s consider the reverse operation: pattern matching. You write a simple match animal with `kitten -> "meow" | `walrus -> "bukkit": the type of `animal will be computed as [< `kitten | `walrus].

The difference between [< ..], [..] and [> ..] is obvious in terms of unification:

  • [< `a | `b | `c] can only be unified with variant types that contain less constructors than itself. So, it can be unified with [ `a | `b ] (which yields [ `a | `b ]) but not [ `a | `d ]. The reason is quite plain: if you were allowed to introduce `d into the variant type, then you could run the above match with animal = `owl and break the code. In short, [< ..] means “any others are forbidden”.
  • [> `a | `b | `c ] can only be unified with variant types that contain more constructors than itself (including other open types). So, it could be unified with [> `a | `d] (which yields [> `a | `b | `c | `d]) but not with [ `a | `b ] (which does not allow `c). In short, [> ..] means “these are allowed”.
  • [`a | `b] (a closed type) is both [< `a | `b] and [> `a | `b] at the same time. It can only be unified with variant types that allow those two but no others, and cannot be unified with any closed types other than itself.

This works, but it’s not perfect.

One major issue is the fact that every variable has a single type. So, suppose you want to transform `b into `c but leave any other constructors untouched. You would then write function `b -> `c | x -> x, but that would be a mistake: being present in the pattern matching means that x is of type [> `b] (it can accept any value, including `b) and being returned means it is unified with the type [> `c] of the other possible return value. So, the type of this function is actually ([> `b | `c] as 'a) -> 'a: even though you replaced every `b with a `c, the type inference insists that the result type can still contain a `b… In fact, there is no way to write such a function because there is no way to express the type of that function in Objective Caml.

Type Coercion

Another issue is that sometimes, you need unification between two different closed types. Consider this short example:

let string_of_vehicle : [`car | `train | `bike]       -> string = (* ... *)
let is_heavy       : [`car | `bike | `cake | `anvil ] -> bool   = (* ... *)

List.map (fun x -> if is_heavy x then string_of_vehicle x else "") [ `car ; `bike ]

This code should work, because I’m working on a car and a bike, both of which can be passed safely to both string_of_vehicle and is_heavy. However, due to external factors, the types of string_of_vehicle and is_heavy are closed and x has to unify with both. In such a situation, the solution in Objective Caml is to allow the user to make the conversion explicit by use of type coercion. In this example, it would look like this:

List.map (fun x ->
  if is_heavy (x :> [`car | `bike | `cake | `anvil ])
  then string_of_vehicle (x :> [`car | `train | `bike])
  else ""
) [ `car ; `bike ]

Coercion works by allowing a [<..] to be treated temporarily as a [..] — the solution above works by using two coercions to unify x as being of type [< `car | `bike], which is compatible with both types that it is coerced to. This is, for all purposes, merely an explicit way to convert a value from a type to a supertype (“any square is a rectangle”) that is not otherwise possible in the unification-based world of Objective Caml types — unification can only work with type equality, not with types being supertypes of other types.

Supertypes, and coercion, are intricately tied to loss of information. When you convert from [<`car | `bike] to [`car | `train | `bike], you are discarding the information that your value can not be a `train. While this loss of information might seem innocuous, it does prevent you from passing that same [<`car | `bike] for a [`car | `bike | `cake | `anvil], because the latter does not allow `train and you just discarded the information that `train cannot happen…

Since loss of information can be deadly to the soundness of a type inference algorithm, the Objective Caml design decision was that all loss of information (converting a type to a supertype) should happen explicitly (by means of a coercion).

Of course, coercion is only usually required if you have closed types in your program. Try avoiding them as much as you can, and you will find that you don’t actually need coercions all that often.

The Original Problem

Back to Tomasz Wegrzanowski’s issue: he was writing a compiler that needed to manipulate both scalars and vectors. So, he defined a value type:

type value = [`Scalar of float | `Vector of float * float * float]

He also defined specific types in order to ensure type-safety of some operations:

type scalar = [`Scalar of float ]
type vector = [`Vector of float * float * float]

type operation = [ `Muliply of scalar * scalar | `DotProduct of vector * vector ]

What he wanted to achieve was to extract all the values available within an operation:

let values : operation -> value list = function
| `Multiply (a,b) ->  [a;b]
| `DotProduct (a,b) -> [a;b]

Of course, this function is discarding precious type information about the values, which requires a type cast. The naive solution would be to individually type-cast a and b throughout the function, but this would be quite tedious. Instead, let’s strike at the root by coercing the function argument itself: by discarding information before the pattern matching occurs, we allow the code to compile with a single coercion. So, what do we coerce the argument to?

It’s not easy to tell. I guess I could create a copy of the operation type and replace all types with value, but that would be quite tedious. Instead, let’s rely on a simple property : when you need to coerce a value in order to pass it to a function, you need to coerce it to the type of that function argument. And the type of the function argument can be obtained through type-inference. Simply write this and compile it:

let values : 'a -> value list = function
| `Multiply (a,b) ->  [a;b]
| `DotProduct (a,b) -> [a;b]

let _ = values ()

The compiler determines the type of 'a through unification, knows that it’s not unit, and generates an error that describes the actual expected type. We can copy-paste it from the compiler output directly!

type general_operation = [ `Multiply of value * value | `DotProduct of value * value ]

let values x = match (a : operation :> general_operation) = with
| `Multiply (a,b) ->  [a;b]
| `DotProduct (a,b) -> [a;b]

let _ = values ()

Should the type operation change, an error will immediately happen in the definition of values (as the coercion from operation to general_operation would fail), which provides the new type that should be pasted on top of general_operation.



1342 feed subscribers
(readers who polled a feed this week)