I have been using OCaml at work for a year now, so here are some reasons why I would not want to use it for software development.
Static type checking helps detect quite a few errors which is very good. Alas, this may lull you into a false sense of security ("it compiles, so it must work correctly"), especially after you spend hours trying to satisfy the compiler (think of higher order functions taking higher order functions as arguments - see below for a simple example).
The bigger trouble is that to always be able to determine the type of every expression, OCaml has a very poor type zoo, e.g., no matrices, only one floating point type etc. CMUCL compilation notes can detect more subtle type problems than those detected by OCaml (e.g., argument falling outside an integer range).
The biggest trouble is that integer overflow is not detected even at run run time, which places OCaml right next to C and assembly with respect to type checking. One may argue that this is not a type issue (and you might even be right technically), but my retort is that the mathematical integers do not overflow, and I expect a language to provide something at least somewhat resembling them (and no, I cannot always use bignums - they are slow and cannot be used directly in arithmetics, see below).
Another subtle issue (which cuts both ways, of course) is that you
cannot easily modify the behavior of a module outside of it.
E.g., suppose you have a Time module which defines and
extensively uses Time.date_of_string which parses the
ISO8601 basic format ("YYYYMMDD").
Suppose you need the full power of the module, but recognizing the
ISO8601 extended format ("YYYY-MM-DD").
Tough luck: you have to get the module maintainer to edit the
function Time.date_of_string - you cannot redefine the
function yourself in your module.
Semantically, OCaml compiles all functions
INLINE (functions defined in functors are
an exception to this rule, of course).
The conspicuous absence of macros cannot be obscured by the presence of preprocessors.
What is the replacement for
WITH-OPEN-FILE?
let call_with_open_input_file name func =
let ic = open_in name in
let res =
try func ic
with exn -> close_in ic; raise exn in
close_in ic;
res
(Do not forget the matching call_with_open_output_file!)
Now, how many OCaml programmers actually write their code this way?
How many of them simply forget to close their channels?
(This is a common hard-to-detect error because the OS closes the channels on
program termination, so the problem rarely manifests itself).
How many of them implement their very very own
UNWIND-PROTECT?
How many do that correctly?
Another area is places (AKA generalized references).
OCaml offers incr to increment a reference to an int.
How about floats? int64? Array elements?
Mutable record fields? You have to write everything in full.
Instead of my_record.my_field += increment (C)
or (incf (record-field my-record) increment) (Lisp) you write
my_record.my_field <- my_record.my_field + increment.
Note that C offers syntactic sugar (+=)
while Lisp uses a regular macro
(setf) which allows one to define new places.
OCaml has separate function := for references and
syntax <- for arrays and records (yes,
OCaml has to handle these situations separately!).
No doubt the following behavior has some reasons behind it (I do not presume the OCaml creators to be malicious) but the reasons I heard so far indicate bad design. Note that OCaml is a new language designed from scratch, so the authors were not constrained by backwards compatibility considerations (that plagued the Common Lisp standardization process and imposed some ugliness on it), the mess we have stems from "new" (as opposed to "legacy") mistakes.
contents!)else belong to it.int to a float - need an
explicit cast. This is an obvious deficiency, but there are more
subtle ones. E.g, the following code does not work:type t = MyInt of int | MyFloat of float | MyString of string let foo printerf = function | MyInt i -> printerf string_of_int i | MyFloat x -> printerf string_of_float x | MyString s -> printerf (fun x -> x) sbecause the first statement makes OCaml think that
foo
has type (int -> string) -> int -> unit
instead of the correct ('t -> string) -> 't -> unit.
(Yes, there are many workarounds, which make the problem look
even more ugly).
In general, this type collapse makes it impossible to use
polymorphic functions with higher order functional arguments
(like printerf above) with variant types.List.map2 but no Array.map2.
There are Array.mapi and Array.iteri, but
no Array.fold_lefti.
Somehow :: is a syntax, not an infix version of
the (nonexistent!) function List.cons.~
arguments are next to useless.~ arguments suckValues do not match: val foo : ?arg:string -> unit -> unit is not included in val bar : unit -> unitso one has to use
(fun () -> foo ()) instead of
foo.
let max_len ?(key=(fun x -> x)) strings =
Array.fold_left ~init:0 strings ~f:(fun acc s ->
max acc (String.length (key s)))
the type of key is string -> string,
not 'a -> string as it would have been if the argument were
required.f has type ?a:int -> b -> c -> d
then f b has type c -> d, not
?a:int -> c -> d as one might have expected
from the fact that one can write f b ~a:1 c.
Moreover, types a:int -> b:int -> unit and
b:int -> a:int -> unit are incompatible even though
one can apply functions of these types to the exact same argument lists!
Moreover, until 3.10, functions foo and bar
compiled while baz did not:let foo l = ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -> s + x) l let bar = (fun l -> ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -> s + x) l) let baz = ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -> s + x)Why? Actually, the latter is a good sign: Ocaml is improving!
float,
int, and int64 (and no automatic type conversion!)
Additionally, functions take a fixed number of arguments, so, to multiply
three numbers, you have to call Int64.mul twice.
Pick your favorite:
(/ (- (* q n) (* s s)) (1- n))
(q * n - s * s) / (n - 1)
(Int64.to_float (Int64.sub (Int64.mul q (Int64.of_int n)) (Int64.mul s s))) /. (float n)The above looks horrible even if you
open Int64:
(to_float (sub (mul q (of_int n)) (mul s s))) /. (float n)which is not a good idea because of silent name conflict resolution. An alternative is to define infix operators:
let ( +| ) a b = Int64.add a b let ( -| ) a b = Int64.sub a b let ( *| ) a b = Int64.mul a b (Int64.to_float ((q *| (Int64.of_int n)) -| (s *| s))) /. (float n)but this comes dangerously close to the horrors of "redefining syntax" (AKA "macro abuse") while not winning much in readability.
A and B both define t
(very common type name!), and you open both modules,
you are not warned that one of t's is shadowed.
Moreover, there is no way to figure out in the top-level
which module defines a specific variable foo.f (g ()) (h ())
(that's (f (g) (h)) for those who hate the extra parens),
h is called before g.
This is done for the sake of speed and because in the pure functional world
the evaluation order is inconsequential. Note that OCaml is not a
pure functional language, so the order does matter!(with-open-file (f "foo" :direction :output) (write x :stream f :readable t :pretty t))with Ocaml:
call_with_open_output_file "foo" (fun f ->
output_string f
(Sexp.to_string_hum
(sexp_of_list (sexp_of_pair sexp_of_foo sexp_of_bar) x)))
Note that OCaml has more parens than Lisp!
This lack of pre-defined generic printing also complicates debugging
(nested structures are hard to print) and interactive development.As all languages defined by their unique implementations, OCaml the language inherits all the warts of OCaml the implementation.
gdb: I run Emacs under it at all times.)
This lossage is mitigated by the presence of the top-level (REPL) though.I think these flaws illustrate the prevalence of the implementer perspective in the design of OCaml as opposed to the user perspective.
round is absentround incorrectly like this:
let round x = truncate (x +. 0.5) (* BROKEN! *)(forgetting about the negative numbers) instead of the correct
let round x = int_of_float (floor (x +. 0.5))
List.map is not tail-recursive!For balance, I must mention some points where OCaml wins:
printf "%d" 3.14 is very nice.
Getting an error on printf "%f" 3 is not so nice.ocamlbuild which is
allegedly similar to asdf.
Alas, the current (3.09) ocamldep sometimes misses some
dependencies (well, all software has bugs).DESTRUCTURING-BIND.Java/C/C++/C#/Perl are even worse!
There appears to be just one good tool,
with OCaml coming a distant second.| Sam Steingold<sds@podval.org> | created: 2007-01-31 |