Hacker News new | ask | show | jobs
by Buttons840 3709 days ago
I was prompted to try an OCaml tutorial by this HN post. I soon saw that strings and numbers each have a different print function. Is this part of the cruft you mention? Why have two print functions?
4 comments

When it comes to functions, there's ad hoc polymorphism, and there's parametric polymorphism.

Parametric polymorphism is when you can define a function without knowing the precise type of its parameters. However, this limits what you can actually do with the parameters.

For instance, a function concat([a], [a]) -> [a] which takes two lists containing items of type "a" knows enough about the type of its parameters (they're lists) to do its job, but it doesn't know everything about them. This is the type of polymorphism OCaml supports. Think of it as "one implementation, many types". That's why OCaml has more than one print function, and why it has integer and floating point versions of operators like + and *.

Ad hoc polymorphism is when you can define implementations for a single function for whatever types you like. So you can implement add(Int, Int) -> Int, as well as add(Float, Float) -> Float. So this is "many implementations, many types." Haskell supports a version of this through typeclasses. OCaml doesn't currently support it, but the new "modular implicits" feature, as I understand it, will provide largely the same functionality.

This is incorrect. Haskell and OCaml both support parametric polymorphism, but Haskell also supports ad-hoc polymorphism, whereas OCaml does not. Modular implicits, it seems, are OCaml's solution to the problem of reducing the set of valid types in a polymorphic function.

Haskell: concat :: [a] -> [a] -> [a]

OCaml: val concat : 'a list -> 'a list -> 'a list = <fun>

Both are parametrically polymorphic. However, in Haskell, I can use a type class to support ad-hoc polymorphism:

concat :: (Num a) => [a] -> [a] -> [a]

Now concat works forall a, as long as a is an instance of Num. This is important for arithmetic operators:

(+) :: (Num a) => a -> a -> a

Now I can have (+) be defined in a meaningful way, since Int, Integer, Float, etc. are all instances of Num, and implement (+). An easy way to think of this (since it's all it is, really) is operator/method overloading. In Haskell, (+) is overloaded for every type that you would want it to work for (the details of this are actually probably different, but are not important for learning).

In OCaml, if I have

val (+) : 'a -> 'a -> 'a = <fun>

There is actually only two implementations of that function:

let (+) a b = a

or

let (+) a b = b

Since I cannot restrict the set of types to the function, I can't do anything with the arguments. I don't know what the types are! Ad-hoc polymorphism allows for restricting the set of types, which allows for Haskell to use + for any Num-like thing.

I also don't see the disagreement between your comment and the parent.
I appreciate the clearer explanation, but I'm not sure what I said that was incorrect. Can you explain?
Hm, after re-reading a couple times, I think I merely misinterpreted how you said "OCaml supports parametric polymorphism" and "Haskell supports ad-hoc polymorphism." Based on your wording I thought you were saying that Haskell only supports ad-hoc polymorphism and OCaml only supports parametric polymorphism. Surely there was something in my head at 2am that made me think you were off in your description, but I don't know what it was. Sorry! I would edit it if I could.

EDIT: I think the line that stood out to me as being odd was "That's why OCaml has more than one print function, and why it has integer and floating point versions of operators like + and *." which was after you explained what parametric polymorphism was. So my interpretation was "OCaml supports parametric polymorphism, which is why there are multiple addition functions for different types." Of course, the real reason isn't due to supporting parametric polymorphism, but specifically because OCaml lacks support for ad-hoc polymorphism.

Interestingly, OCaml does have sort of pseudo-ad-hoc polymorphic implementations for comparison operators.

val (<) : 'a -> 'a -> bool = <fun>

But this is a wonderfully hacky implementation.

Cool, thanks for the clarification! It was a late night for me too so I wasn't as clear as I thought I was being.
It's annoying I guess, but you get over it in a few hours. Interestingly it's only annoying from an ideological standpoint—it's never bothered me when reading or writing code.

Anyway, the reason, along with having separate sets of arithmetic operators, is basically because Ocaml ‘generics’ can't be specialized like C++ templates can. Doing it this way keeps the whole language fully type-inferable.

It is actually annoying from a very practical point of view. You cannot, for example, write an algorithm that will work an any sequence type, and then apply it to a list. This is why, for instance, you have List.map and so on
You can, that's precisely why we have functors and for this kind of use cases (abstracting a module over another), they are much nicer than type classes.
As far as I understand, this would only work if standard sequence types were defined inside a functor, which is not the case.
The Ocaml std library is a bit lackluster and people often replace it with something more featureful.

BTW, I think https://github.com/c-cube/sequence is more in closer to the generic iterable datatype you are looking for. You still need to convert to and from the seq type but its pretty close...

The biggest reason is because of type inference. Ocaml generally stays away from type coercions or overloading.

That said, Ocaml does have some voodoo magic for type-safe printf, so in the specific case of printing you can use a single function if you want

    let name = "Buttons840" in
    Printf.printf "Hello, %s!\n" name
This is exactly the problem that modular implicits will solve. See http://arxiv.org/pdf/1512.01895.pdf