Wednesday, March 23, 2005
Partial functions in OCAML
OCAML supports something called "partial functions". Suppose you define a function that takes two arguments and adds them together:
You can obviously call adder 2 3 and expect to get back 5. OCAML lets you call adder 2 and get back a function that adds 2 to whatever argument you pass it. For example:
You would see "7 10" printed out.
I must admit, my first reaction to this was "Neat. So what?" I am starting to see the usefulness of it, however. In Scheme, if I want to loop through a list and add 5 to every member of the list, I can do something like this:
In OCAML, you could do this:
Since I have defined my adder function, though, I can do this instead:
I think the trick to taking advantage of partial functions may be to determine which arguments are likely to change and place those last. For example, if I have a function called heap_consolidate, I can define it as:
or I can do:
If I am going to be passing a lot of different x values for the same heap, especially if I want to iterate through a list or an array, I might want to make heap the first parameter so I can do something like this:
If heap is the second argument, I would have to do:
While partial functions may seem error-prone, I don't think that is the case. If you accidentally omit a function argument, OCAML's type checker will notice that you are using a function type where it is expecting you to use something else. Without type checking, partial functions seem pretty dangerous.
let adder x y = x + y;;
You can obviously call adder 2 3 and expect to get back 5. OCAML lets you call adder 2 and get back a function that adds 2 to whatever argument you pass it. For example:
let add2 = adder 2;;
let add3 = adder 3;;
Printf.print "%d %d\n" (add2 5) (add3 7);;
You would see "7 10" printed out.
I must admit, my first reaction to this was "Neat. So what?" I am starting to see the usefulness of it, however. In Scheme, if I want to loop through a list and add 5 to every member of the list, I can do something like this:
(map (lambda (x) (+ x 5)) '(1 2 3 4 5 6))
In OCAML, you could do this:
List.map (function x -> (x + 5)) [1;2;3;4;5];;
Since I have defined my adder function, though, I can do this instead:
List.map (adder 5) [1;2;3;4;5];;
I think the trick to taking advantage of partial functions may be to determine which arguments are likely to change and place those last. For example, if I have a function called heap_consolidate, I can define it as:
let heap_consolidate x heap = (* do something with x and the heap *)
or I can do:
let heap_consolidate heap x = (* do something with x and the heap *)
If I am going to be passing a lot of different x values for the same heap, especially if I want to iterate through a list or an array, I might want to make heap the first parameter so I can do something like this:
List.map (heap_consolidate myheap) myheap.root;;
If heap is the second argument, I would have to do:
List.map (function x -> heap_consolidate x myheap) myheap.root;;
While partial functions may seem error-prone, I don't think that is the case. If you accidentally omit a function argument, OCAML's type checker will notice that you are using a function type where it is expecting you to use something else. Without type checking, partial functions seem pretty dangerous.