Automated reasoning in F#, Scala, Haskell, C++, and Julia

Luckily for us, we won't have to remember any elementary school arithmetic,
because Harris' excellent Handbook of Practical Logic and Automated Reasoning begins with a
simple algorithm to do exactly that. It's not complicated, but it's a pretty
good barometer of how painful a programming language will be for the kind of
hybrid (probabilistic logic, or statistical relational) approaches I work with.
Here, I compare the implementations of Harris' simple algorithm in F#, Scala,
Haskell, C++, and Julia.

No programming languages were hurt while writing this post. It's not a
competition, and I avoided talking about languages I dislike. Sum types are
discussed in length because they are awesome and useful for this problem
(and many, many others).

The ML family

Harris' book uses OCaml, a popular language for solvers. F#, Haskell, and
Scala all share roots with OCaml, with F# being the closest thing to an OCaml
dialect. I'll start with F#:

It's almost the same as the OCaml version in Harris' book. The key trick is
to define an expression (Expr) as a variable (string) or a constant
(integer) or an addition or a multiplication (both made of two
expressions). The or is important, object-oriented programming languages
focuses on hierarchies of objects, while sum types define a type as a series of
alternatives. Sum types are important for another reason: they provide
an easy way to express things like "this function might return
an integer", for example in Haskell if we want a data structure that maps
keys to values:

The point is that a key-value store will only return a value if the key is
present. In this example, the map takes a country (string) and returns its
capital (string). However, when we try to take a value from the map with the
lookup function, Haskell returns a Maybe type with either Just
String, if the string provided is found in the map, or Nothing if
the key is absent. We then use pattern matching to deal with these
possibilities in the lookupCapitals function. One of the most common mistake in
programming is to return a null and not deal with it properly. The solution
with sum types is to return a wrapped value and handling possibilities
explicitly with pattern matching. It solves with types what many languages
would solve with exceptions and try-catch apparatuses.

It's quite similar to F#. I decided to add types explicitly to
simplify1 and simplify, but Haskell is smart enough to deduce the
type without this. Arguably the only thing worth explaining is the $ operator.
The operator forces Haskell to evaluate the expression to the right of the
operator in priority, and if it reminds you of parentheses, you are absolutely
right. x and y have the same value here:

x = log (sqrt (exp 5.0))
y = log $ sqrt $ exp 5.0

The operator is there to reduce visual clutter. In my opinion, F# is easier
to read because the |> operator enforces left-to-right reading, which is more
natural than reading code inside-out:

Everything is an object in Scala. Thus, we have to define the functions to
simplify as methods inside a singleton object. I named the functions
evalOne and eval since it has a bit odd to have a function named
simplify inside a Simplify object.

C++

Few understand every corner of C++'s monstrous standard. It's huge. Surely,
with so many features, there must something to solve this
simple problem cleanly. Well... no. It's a well-known lacuna with C++,
see Open and
Efficient Type Switch for C++ for a library built to implement
pattern matching (the effort is directed by the creator
of the C++ language). That said, here I'll use the boost library (A)
because solutions based only on the standard library are contrived and
(B) because boost is almost standard, and I don't want to rely on third-party
libraries.

This is boost::variant in action. My biggest qualm with this type of clever
header-heavy code is that you get to see a big chunk of the developers'
lifework unroll before your eyes every time a small mistake is made. Otherwise
it's an OK substitute for proper sum types/pattern matching. If you want to know
how this code works, you need to read a bit on the visitor pattern.

Julia

Julia is an attempt to build a fast and flexible replacement for
R/Python/Matlab. An issue with most dynamic languages is that there is no
elegant way to switch on type. To be fair, you cannot really do it with most
static languages either, see previous section... However, Julia supports
multiple-dispatch based on type annotation. To be clear, it's quite different
from the F#/Scala/Haskell approach. In these languages, it is possible to define
sum types and do pattern matching on their constructors. With Julia, we define a
function with type annotation and let the interpreter dispatch based on runtime
type information. Multiple dispatch is supported in Julia for performance: it
allows the interpreter to compile optimized functions and use the best one,
adding predictability while keeping the language dynamic (for some reason...).
Here's the algorithm in Julia:

abstract Expr
type Const

Unlike pattern matching, we can only dispatch on type, so we need an if
expression (the ? operator in Julia, just like C), or I could've used the match
macro, but it's overkill here. It's not too inelegant, and at first I thought
it was a good enough way to simulate sum types and pattern matching. It matters
to the Julia ecosystem because these features are very useful to build solvers,
logic and theorem proving systems, etc etc. Pretty nice for a technical
computing platform. Unfortunately, while Julia does well with this simple example,
I think an oddity with the language would soon bite us: return type declarations
are not allowed, and yes, it is a big deal.

First, it's a question of correctness: you can return a float thinking
you're returning an integer. That's awful. Also, since annotation is not
allowed for the return value, it's also impossible to add annotation for a
higher-order function (a function taking functions as input). As a concrete
example, first-order logic has predicates mapping objects to a boolean,
and functions mapping objects to objects. We'd like to do: