Skip to content

Instantly share code, notes, and snippets.

@vindarel
Last active February 12, 2020 13:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vindarel/16faaa3db5ecad88f23d2242e81046c3 to your computer and use it in GitHub Desktop.
Save vindarel/16faaa3db5ecad88f23d2242e81046c3 to your computer and use it in GitHub Desktop.
OCaml sucks (and some comparison to Lisp)
<body>
<html>
<a href="http://www.podval.org/~sds/ocaml-sucks.html"> original url</a>
<a href="https://web.archive.org/web/20080203114732/http://www.podval.org/~sds/ocaml-sucks.html"> Wayback machine</a>
<a href="https://www.reddit.com/r/lisp/comments/5wk7e0/ocaml_sucks_and_some_comparison_to_lisp/"> /r/lisp comments</a>
<a href="https://www.reddit.com/r/programming/comments/cy0wh/ocaml_sucks_still_relevant/"> Is this article (still) relevant?</a>
<h1>OCaml Language Sucks</h1>
<p>I have been using <a href="https://web.archive.org/web/20080203114732/http://caml.inria.fr/">OCaml</a> at work
for a year now, so here are some reasons why I would not want to use
it for software development.</p>
<ol><li><a href="#static">Static type checking</a></li>
<li><a href="#macros">No Macros</a>
<ol><li><a href="#wrappers">Wrappers</a></li>
<li><a href="#places">Places</a></li></ol></li>
<li><a href="#language">Minor Language Suckiness</a></li>
<li><a href="#implementation">OCaml Implementation Sucks</a></li>
<li><a href="#library">Standard Library Sucks</a></li>
<li><a href="#rocks">OCaml Language Rocks</a></li>
<li><a href="#caveats">Caveats</a></li>
<li><a href="#links">Relevant Links</a></li></ol>
<h2 id="static">Static type checking</h2>
<p>Static type checking helps detect quite a few errors which is very good.
Alas, this may lull you into a false sense of security
(<cite>"it compiles, so it must work correctly"</cite>),
especially after you spend <em>hours</em> trying to satisfy the compiler
(think of higher order functions taking higher order functions as
arguments - see <a href="#no-poly">below</a> for a simple example).</p>
<p>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.
<a href="https://web.archive.org/web/20080203114732/http://www.cons.org/cmucl/">CMUCL</a> compilation notes can
detect more subtle type problems than those detected by OCaml (e.g.,
argument falling outside an integer range).</p>
<p>The biggest trouble is that integer overflow is <em>not</em> detected
even at <em>run</em> 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 <em>not</em> 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).</p>
<p>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 <code>Time</code> module which defines and
extensively uses <code>Time.date_of_string</code> which parses the
ISO8601 basic format (<code>"YYYYMMDD"</code>).
Suppose you need the full power of the module, but recognizing the
ISO8601 extended format (<code>"YYYY-MM-DD"</code>).
Tough luck: you have to get the module maintainer to edit the
function <code>Time.date_of_string</code> - you cannot redefine the
function yourself in your module.
<em>Semantically</em>, OCaml compiles all functions
<a href="https://web.archive.org/web/20080203114732/http://www.lisp.org/HyperSpec/Body/dec_inlinecm_notinline.html"><code>INLINE</code></a> (functions defined in <em>functors</em> are
an exception to this rule, of course).</p>
<h2 id="macros">No Macros</h2>
<p>The conspicuous absence of macros cannot be obscured by the presence of
preprocessors.</p>
<h3 id="wrappers">Wrappers</h3>
<p>What is the replacement for
<a href="https://web.archive.org/web/20080203114732/http://www.lisp.org/HyperSpec/Body/mac_with-open-file.html"><code>WITH-OPEN-FILE</code></a>?</p>
<pre>
let call_with_open_input_file name func =
let ic = open_in name in
let res =
try func ic
with exn -&gt; close_in ic; raise exn in
close_in ic;
res
</pre>
<p>(Do not forget the matching <code>call_with_open_output_file</code>!)</p>
<p>Now, how many OCaml programmers actually write their code this way?
How many of them simply <em>forget</em> 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
<a href="https://web.archive.org/web/20080203114732/http://www.lisp.org/HyperSpec/Body/speope_unwind-protect.html"><code>UNWIND-PROTECT</code></a>?
How many do that <em>correctly</em>?</p>
<h3 id="places">Places</h3>
<p>Another area is <a href="https://web.archive.org/web/20080203114732/http://www.lisp.org/HyperSpec/Body/sec_5-1.html">places (AKA generalized references)</a>.
OCaml offers <code>incr</code> to increment a reference to an <code>int</code>.
How about <code>float</code>s? <code>int64</code>? Array elements?
Mutable record fields? You have to write everything in full.
Instead of <code>my_record.my_field += increment</code> (C)
or <code>(incf (record-field my-record) increment)</code> (Lisp) you write
<code>my_record.my_field &lt;- my_record.my_field + increment</code>.</p>
<p>Note that C offers syntactic sugar (<code>+=</code>)
while Lisp uses a regular macro
(<a href="https://web.archive.org/web/20080203114732/http://www.lisp.org/HyperSpec/Body/mac_setfcm_psetf.html"><code>setf</code></a>) which allows one to define new places.
OCaml has separate function <code>:=</code> for references and
syntax <code>&lt;-</code> for arrays and records (yes,
OCaml <em>has</em> to handle these situations separately!).</p>
<h2 id="language">Minor Language Suckiness</h2>
<p>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 <em>backwards compatibility</em>
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.</p>
<dl>
<dt><strong>Record field naming hell</strong></dt>
<dd>Cannot have two record types in the same file that have a field
with the same name (and before version 3.09, you <em>could not</em>
have a record field named <code>contents</code>!)</dd>
<dt><strong>Syntax</strong></dt>
<dd>Pretty much unreadable; especially the bizarre rules to decide how
many expressions after <code>else</code> belong to it.</dd>
<dt id="no-poly"><strong>No Polymorphism</strong></dt>
<dd>Cannot add an <code>int</code> to a <code>float</code> - need an
explicit cast. This is an obvious deficiency, but there are more
subtle ones. E.g, the following code does not work:<pre>
type t = MyInt of int | MyFloat of float | MyString of string
let foo printerf = function
| MyInt i -&gt; printerf string_of_int i
| MyFloat x -&gt; printerf string_of_float x
| MyString s -&gt; printerf (fun x -&gt; x) s
</pre> because the first statement makes OCaml think that <code>foo</code>
has type <code>(int -&gt; string) -&gt; int -&gt; unit</code>
instead of the correct <code>('t -&gt; string) -&gt; 't -&gt; unit</code>.
(Yes, there are <em>many</em> workarounds, which make the problem look
even more ugly).
In general, this <em>type collapse</em> makes it impossible to use
polymorphic functions with higher order functional arguments
(like <code>printerf</code> above) with variant types.</dd>
<dt><strong>Inconsistent function sets</strong></dt>
<dd>There is <code>List.map2</code> but no <code>Array.map2</code>.
There are <code>Array.mapi</code> and <code>Array.iteri</code>, but
no <code>Array.fold_lefti</code>.
Somehow <code>::</code> is a <em>syntax</em>, not an infix version of
the (nonexistent!) function <code>List.cons</code>.</dd>
<dt><strong>No dynamic variables</strong></dt>
<dd>Without <a href="https://web.archive.org/web/20080203114732/http://www.lisp.org/HyperSpec/Body/sec_3-1-2-1-1-2.html">dynamic</a> variables for default values, optional <code>~</code>
arguments are next to useless.</dd>
<dt><strong>Optional <code>~</code> arguments suck</strong></dt>
<dd><ul><li>The default values of the optional arguments cannot depend on
positional arguments because of the calling conventions: to make function
call delimiters optional, every function call for a function with optional
arguments is required to end with a positional argument.</li>
<li><pre>
Values do not match:
val foo : ?arg:string -&gt; unit -&gt; unit
is not included in
val bar : unit -&gt; unit
</pre>so one has to use <code>(fun () -&gt; foo ())</code> instead of
<code>foo</code>.</li>
<li>The default values of optional arguments restrict the possible type the
argument may take. E.g., in<pre>
let max_len ?(key=(fun x -&gt; x)) strings =
Array.fold_left ~init:0 strings ~f:(fun acc s -&gt;
max acc (String.length (key s)))
</pre>the type of <code>key</code> is <code>string -&gt; string</code>,
not <code>'a -&gt; string</code> as it would have been if the argument were
required.</li></ul></dd>
<dt><strong>Partial argument application inconsistency</strong></dt>
<dd>If <code>f</code> has type <code>?a:int -&gt; b -&gt; c -&gt; d</code>
then <code>f b</code> has type <code>c -&gt; d</code>, not
<code>?a:int -&gt; c -&gt; d</code> as one might have expected
from the fact that one can write <code>f b ~a:1 c</code>.
Moreover, types <code>a:int -&gt; b:int -&gt; unit</code> and
<code>b:int -&gt; a:int -&gt; unit</code> are incompatible even though
one can apply functions of these types to the exact same argument lists!
Moreover, until 3.10, functions <code>foo</code> and <code>bar</code>
compiled while <code>baz</code> did not:<pre>
let foo l = ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -&gt; s + x) l
let bar = (fun l -&gt; ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -&gt; s + x) l)
let baz = ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -&gt; s + x)
</pre>Why? Actually, the latter is a <em>good</em> sign:
Ocaml <em>is</em> improving!</dd>
<dt><strong>Arithmetic's readability</strong></dt>
<dd>Lisp is often blamed for its "unreadable" representation of arithmetic.
OCaml has separate arithmetic functions for <code>float</code>,
<code>int</code>, and <code>int64</code> (and no automatic type conversion!)
Additionally, functions take a fixed number of arguments, so, to multiply
three numbers, you have to call <code>Int64.mul</code> <em>twice</em>.
Pick your favorite:
<pre>(/ (- (* q n) (* s s)) (1- n))</pre>
<pre>(q * n - s * s) / (n - 1)</pre>
<pre>(Int64.to_float (Int64.sub (Int64.mul q (Int64.of_int n)) (Int64.mul s s))) /. (float n)</pre>
The above looks horrible even if you <code>open Int64</code>:
<pre>(to_float (sub (mul q (of_int n)) (mul s s))) /. (float n)</pre>
which is <em>not</em> a good idea because
of <a href="#name-conflict">silent name conflict resolution</a>.
An alternative is to define infix operators: <pre>
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)
</pre>but this comes dangerously close to the horrors of "redefining
syntax" (AKA "macro abuse") while not winning much in readability.</dd>
<dt id="name-conflict"><strong>Silent name conflict resolution</strong></dt>
<dd>If modules <code>A</code> and <code>B</code> both define <code>t</code>
(<em>very</em> common type name!), and you open both modules,
you are <em>not</em> warned that one of <code>t</code>'s is shadowed.
Moreover, there is no way to figure out in the top-level
which module defines a specific variable <code>foo</code>.</dd>
<dt><strong>Order of evaluation</strong></dt>
<dd>The forms are evaluated <strong>right-to-left</strong> (as they are
popped from the stack), not <em>left-to-right</em> (as they are written
and read). E.g., in <code>f (g ()) (h ())</code>
(that's <code>(f (g) (h))</code> for those who hate the extra parens),
<code>h</code> is called <strong><em>before</em></strong> <code>g</code>.
This is done for the sake of speed and because in the pure functional world
the evaluation order is inconsequential. Note that OCaml is <em>not</em> a
pure functional language, so the order <em>does</em> matter!</dd>
<dt><strong>No object input/output</strong></dt>
<dd>Top-level can print any object, but this functionality is
<em>not</em> available in programs.
E.g., in Lisp one can print <em>any</em> structure to a file
in a human-readable (and editable!) form to be read later.
OCaml has to resort to cumbersome external libraries (like Sexp) to
accomplish something like that. Compare Lisp:<pre>
(with-open-file (f "foo" :direction :output)
(write x :stream f :readable t :pretty t))
</pre>with Ocaml:<pre>
call_with_open_output_file "foo" (fun f -&gt;
output_string f
(Sexp.to_string_hum
(sexp_of_list (sexp_of_pair sexp_of_foo sexp_of_bar) x)))
</pre>Note that OCaml has <em>more</em> parens than Lisp!
This lack of pre-defined generic printing also complicates debugging
(nested structures are hard to print) and interactive development.</dd>
<dt><strong></strong></dt><dd></dd>
</dl>
<h2 id="implementation">OCaml Implementation Sucks</h2>
<p>As all languages defined by their unique implementations,
OCaml the language inherits all the warts of OCaml the implementation.</p>
<dl>
<dt><strong>Compiler stops after the first error</strong></dt>
<dd>Compiler should process the whole compilation unit (normally, a file)
unless a total disaster strikes.
E.g., if an expression has the wrong type, just assume it does and proceed
to the next expression.
You do not have to generate the executable, just report as many errors as
possible!</dd>
<dt><strong>No stack trace for natively compiled executables</strong></dt>
<dd>If you want backtraces, compile to bytecode and sacrifice speed.
(3.10 "fixes" this problem - an incomplete and usually incorrect and
useless backtrace is available even for natively compiled executables).</dd>
<dt><strong>Debugger sucks</strong></dt>
<dd>Code running under debugger is so slow as to be useless.
(Cf. <code>gdb</code>: I run Emacs under it at all times.)
This lossage is mitigated by the presence of the top-level (REPL) though.</dd>
<dt><strong>GC sucks</strong></dt>
<dd>Loading the same data set
in <a href="https://web.archive.org/web/20080203114732/http://clisp.cons.org/">CLISP</a> takes 150kb of RAM, in
OCaml - 900kb, mostly because some intermediate strings used for
parsing are never released (despite major collections and compactions).</dd>
<dt><strong></strong></dt><dd></dd>
</dl>
<h2 id="library">Standard Library Sucks</h2>
<p>I think these flaws illustrate the prevalence of the <em>implementer</em>
perspective in the design of OCaml as opposed to the <em>user</em>
perspective.</p>
<dl>
<dt><strong>Function <code>round</code> is absent</strong></dt>
<dd>This seems like a small fish until you realize that many people
implement <code>round</code> incorrectly like this:
<pre>let round x = truncate (x +. 0.5) (* BROKEN! *)</pre>
(forgetting about the negative numbers) instead of the correct
<pre>let round x = int_of_float (floor (x +. 0.5))</pre></dd>
<dt><strong>Lists</strong></dt>
<dd><ul><li><code>List.map</code> is <em>not</em> tail-recursive!</li>
<li>Lists are <em>immutable</em>, thus cannot be spliced - non-consing
operation is impossible.</li></ul></dd>
<dt><strong></strong></dt><dd></dd>
</dl>
<h1 id="rocks">OCaml Language Rocks</h1>
<p>For balance, I must mention some points where OCaml wins:</p><dl>
<dt><strong>Format control checks</strong></dt>
<dd>Getting an error on <code>printf "%d" 3.14</code> is very nice.
Getting an error on <code>printf "%f" 3</code> is not so nice.</dd>
<dt><a href="https://web.archive.org/web/20080203114732/http://omake.metaprl.org/"><strong>OMake</strong></a></dt>
<dd>Automatic dependency detection is very nice
(note that omake is not tied to ocaml, it can be used for any project,
also, you pay for the automatic dependency detection with speed).
Also, file comparison is done by contents, not mtime, so adding a
comment results in recompilation of just the modified file, not its
dependencies.
Also, the upcoming 3.10 comes with <code>ocamlbuild</code> which is
allegedly similar to <a href="https://web.archive.org/web/20080203114732/http://www.cliki.net/asdf">asdf</a>.
Alas, the current (3.09) <code>ocamldep</code> sometimes misses some
dependencies (well, all software has bugs).</dd>
<dt><strong>Comment syntax</strong></dt>
<dd>Nested comments are nice</dd>
<dt><strong>Speed</strong></dt>
<dd>The generated code is quite fast (as long as you are willing to forgo
<em>all</em> polymorphism and stack traces).
The OCaml developers have made speed their <em>top</em> priority and have
sacrificed a lot to speed up the compiler and the generated code.
They did mostly succeed, although
<a href="https://web.archive.org/web/20080203114732/http://caml.inria.fr/mantis/view.php?id=4053">quadratic algorithms</a>
make large (usually generated) polymorphic variant types completely
unusable (<em>hours</em> of compilation time!)</dd>
<dt><strong>Pattern matching</strong></dt>
<dd>This is a very powerful tool, should be easily implemented as a
Lisp macro using
<a href="https://web.archive.org/web/20080203114732/http://www.lisp.org/HyperSpec/Body/mac_destructuring-bind.html"><code>DESTRUCTURING-BIND</code></a>.</dd>
<dt><strong>Functors</strong></dt>
<dd>A nice feature which somewhat mitigates the limitations imposed by
static typing and lack of macros.</dd>
<dt><strong></strong></dt><dd></dd>
</dl>
<h1 id="caveats">Caveats</h1>
<ol><li>Make no mistake: <code>Java/C/C++/C#/Perl</code> are even worse!
There appears to be just one <a href="tool.html">good tool</a>,
with OCaml coming a distant second.</li>
<li>I know a few <em>very</em> smart people who <em>love</em> OCaml,
so my personal opinion that OCaml sucks does not mean that <em>you</em>
will not love it.</li></ol>
<h1 id="links">Relevant links</h1>
<ul>
<li><a href="https://web.archive.org/web/20080203114732/http://t-a-w.blogspot.com/2006/05/variant-types-in-ocaml-suck.html">Variant types in OCaml suck</a></li>
<li><a href="https://web.archive.org/web/20080203114732/mailto:sds@podval.org?subject=ocaml-sucks.html">add to
this list</a></li>
</ul>
<p><br/><br/></p><hr/>
<table width="100%"><tr><td align="left">
<a href="sds.html">Sam Steingold</a><address><a href="https://web.archive.org/web/20080203114732/mailto:sds@podval.org?subject=ocaml-sucks.html">&lt;sds@podval.org&gt;</a></address></td>
<td align="center"><a href="https://web.archive.org/web/20080203114732/http://validator.w3.org/check/referer"><img src="https://web.archive.org/web/20080203114732im_/http://www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML" height="31" width="88"/></a>
<a href="https://web.archive.org/web/20080203114732/http://www.w3.org/Style/CSS/Buttons/"><img src="https://web.archive.org/web/20080203114732im_/http://www.w3.org/Style/CSS/Buttons/mwcts" alt="Use CSS" height="31" width="88"/></a>
<a href="https://web.archive.org/web/20080203114732/http://jigsaw.w3.org/css-validator/check/referer"><img src="https://web.archive.org/web/20080203114732im_/http://jigsaw.w3.org/css-validator/images/vcss" alt="Valid CSS" height="31" width="88"/></a></td>
<td align="right">created: <strong>2007-01-31</strong><br/>
<script type="text/javascript">document.write(updated());</script>
</td></tr></table>
<!-- <p><img src="pub/server.cgi?ocaml-sucks.html"> -->
</body>
</html>
<!--
FILE ARCHIVED ON 11:47:32 Feb 03, 2008 AND RETRIEVED FROM THE
INTERNET ARCHIVE ON 12:44:54 Feb 12, 2020.
JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE.
ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C.
SECTION 108(a)(3)).
-->
<!--
playback timings (ms):
RedisCDXSource: 6.307
captures_list: 1620.478
exclusion.robots.policy: 0.209
esindex: 0.008
LoadShardBlock: 1591.96 (3)
PetaboxLoader3.datanode: 352.346 (4)
load_resource: 1314.604
CDXLines.iter: 15.201 (3)
exclusion.robots: 0.227
PetaboxLoader3.resolve: 2510.297 (2)
-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment