Hacker News new | ask | show | jobs
by dbpatterson 4142 days ago
I think the author doesn't give enough credit to things that OCaml has that Haskell doesn't have: a powerful module system (ie, functors), polymorphic variants/subtyping, etc.
6 comments

There's a bunch of other nice features of OCaml such as named arguments, fast compile times, strictness, c-types, and reasonable records. To achieve (some of the feature in) Elm style "structural subtyping" records in OCaml you use the more verbose "object" keyword which is just a record with row polymorphism. Most choose to stick with standard records because they compile to more efficient code.

I think the ML module system (in OCaml and other languages) is very powerful because it allows you to abstract and operate not only on types but also on values simultaneously. F#, Haskell, and most others lack this but it would be great if they were to officially adopt it. OCaml is also very easy to learn and I seriously recommend Real World OCaml - it's free online and excellent.

Agreed on the ML module system. It's a damn shame that Haskell has this anemic probably-historical-accident namespacing-only module system that we're stuck with. On the plus side, there seems to be some real impetus to implement the Backpack system, though there are still unexplored points in the design space behind Backpack, especially wrt. type classes.

FWIW, I think type classes are actually what make Haskell more appealing to me than O'Caml, even though O'Caml's modules technically subsume type classes (for most purposes anyway). In practice it just gets too verbose.

That, and enforced purity.

One thought on the verbosity of type classes vs. ML modules/functors: As a framework developer, if I have a more powerful abstraction (supposing ML functors/modules really are more powerful), then even if abstracting is more verbose, it might result in an even better end-developer experience, if that explicitness is limited to the core internal part of the framework.

In other words, that verbose application of functors etc, might only need to be written once in the internals of the framework, but could enable more powerful features for users of the framework with little or no additional verbosity. This is just one thing I've noticed happen in a very specific case and it might not be true in general. Really learning Haskell type classes is still on my list of things to do, so please forgive me if I've misspoken.

That was actually my main objection to modules vs. typeclasses: The burden often seemed to land on the users of libraries. Typeclasses are usually effortless as a user, though there may be a multitude of sins[1] hidden behind them.

EDIT: Don't get me wrong, there are also advantages to being able to explcitly declare two structurally identical modules as different, but in practice I find that newtypes suffice.

[1] FlexibleInstances, UndecidableInstances, etc. :)

Total agreement about most of those things, but I want to indicate that Haskell has some amount of row typing available via libraries like Vinyl and it certainly has c-types.
the most recent vinyl version (0.5) https://hackage.haskell.org/package/vinyl-0.5 winds up being a REALLY nice balance of flexibility, good type inference, and a few other things.

Its actually simple enough that for a work project I decided it would be simpler to write a custom version of the same datastructure just to avoid extra deps. (and because I needed some slightly bespoke invariants)

:) Thanks! And yes, I don't know about Anthony, but my intention has always been for Vinyl to be a proof-of-concept for what happens when you try to make a clean, minimal & well-factored HList experience; my motto is, “Now build your own Vinyl”.
Thanks for reminding me. I saw a recent records proposal for Haskell that seemed to hit all the marks including performance (it wasn't Vinyl). I seriously hope OCaml considers a similar approach to records in the future, but in the mean time, "out of the box" records in OCaml are sound and reasonable.
Named arguments may be just "syntax sugar" but its one of the biggest things I miss from Haskell. They make library functions more consistent and they also make it much easier to write point free code because you don't need to resort to combinators like "flip" or "." as much.
I believe they are slightly beyond "syntax sugar". If I'm mistaken, I'd love to see the equivalent of what they desugar to. Some of the nuances that I believe OCaml's named arguments get right (and what greatly distinguishes them from passing a record) may require additional work in the type system beyond simple desugaring to something like records. Someone please correct me if I'm wrong.

- Partially applying arguments. You can apply one named argument, and get a function that expects the remaining named arguments. This is pretty great though confusing when you see it for the first time.

- Defaults for omitted arguments. If the caller doesn't specify an argument, you can define what should be used instead. This is kind of like the opposite of subtyping on record arguments. With structurally subtyped record arguments, you can pass a record that has more information than a certain minimum set of labeled fields. But with named optional arguments with defaults, you can pass less than a certain maximum number of labeled fields.

Also, provable performance properties are a big plus, caused by making lazy evaluation optional rather than mandatory. This aspect is explained quite well by Robert Harper:

https://existentialtype.wordpress.com/2012/08/26/yet-another...

(although he doesn't mention OCaml, and probably has other, even better future languages, in mind)

Naw, Harper is SML-all-the-way (AFAICT).

Btw, Haskell is technically not "lazy", it's "non-strict".

Now, I think he has a good point, but I don't think think there's any general consensus within the FP community which one of non-strict/strict is "better". Personally, I don't think there's a "right" answer.

Earlier on in my career, I would have said that "non-strict/strict" should have a part of the type of a term, but after non-trivial experience with O'Caml and Lazy.t, I'm not so sure. I'm definitely sure that it lead to an absurd proliferation of incompatible interfaces.

And, as SPJ has opined, laziness forces you to be honest about side effects, which is not a trivial thing!

One thing I find interesting, is that our industry is basically a gigantic sea full of Java/C++ programmers.

Within that ocean, there is a small portion of Functional Programmers who have come to a realization that a set of finer grained abstractions would improve the industry productivity at large.

Within that small subset, you have a set of people who believe that static type systems aren't ready for widespread use or are too cumbersome for expressive programming in many cases. While the other part of the functional camp are convinced that modern type systems (usually the ML variety) are more than sufficient for expressive programming.

Within the ML static typing camp you have people who adamantly claim that in order for static type systems to be sufficiently expressive, you must have a particular kind of polymorphism, or that this kind of polymorphism must be implicit - not explicit. Or they might form a stance along the lines of strictness vs non-strictness.

Then within the strictness camp, for example, some people will form a stance that your language must be formally specified (SML) as opposed to having only a reference implementation (OCaml).

At the end of the day, we're left with a handful of people who share our exact opinion about what languages would in theory make the industry more productive.

Meanwhile, the enormous sea of industrial engineers are still using Java/C++.

It can end up looking like people debating which particular brand of natural spring water should be given to millions of people who are thirsty in the desert.

I'm not sure I'm saying anything helpful, and I totally understand having a stance on any one of these issues. I'm not accusing anyone of being too focused on these nuances. They are important questions and I'm thankful people way more studied than I am take the time to report their findings on the tradeoffs. But personally, I also try my best to not loose sight of the fact that we are in a giant sea of people who would benefit from exploring virtually any of functional paradigms/languages.

I won't claim any special knowledge, nor do I have any actual solid research to back up my intuitions.

I can certainly recognize the feeling that O'Caml makes you more productive from when I first discovered it, but that was mostly just because of algebraic datatypes. (And pattern matching which, while not terribly useful in general circumstances, is hugely useful in practical CRUD-like applications.) Polymorphic variants also made the "pro" list.

The biggest boost to my productivity I've ever felt(!) was when explicitly separating different types of effects. (Not just "pure vs. impure", but "uses-network" vs. "uses-filesystem" vs. "pure"... which is why I'm currently sticking with Haskell because it enforces that kind of discipline.

(I'm sure there'll be something better coming along any day now, but...)

Mostly, I hope that problem is mostly a lack of (appropriate) advocacy and education. There's also an absurd amount of inertia which is due to sheer entrenched interests/industries.

EDIT: ... or maybe it's a generational thing. After all this kind of thing happens in every other fast moving discipline without terribly rigorous theoretical underpinnings[1], e.g. medicine or biology.

[1] Don't get me wrong, CS is basically math which is unassailable, but we still have no idea how to (reproducibly) produce stable/well-functioning software.

(Note, not being critical of you, just placing this here because I was thinking about it recently)

Maybe because CS is so young, there is a tendency to confuse theory and practice.

Whether a particular language makes one more productive isn't math, it's engineering. When we talk about how monads might allow you to separate concerns and relieve a mental load -- we are talking engineering. When we talk about how monads compose, we are talking abut the math/science that enables the engineering. They are both related, just like mechanics is related to mechanical engineering, but they aren't the same and they have different concerns.

Be wary when you start thinking in terms of "entrenched interests". It's tempting to go down that road, but the reality is that those "entrenched interests" actually have good engineering reasons to be that way[1]. It isn't like a million other programmers haven't noticed that FP-style programming offers some benefits -- but often the benefits end up not out-weighing the drawbacks in the languages, ecosystems, and practical performance and hardware concerns.

This should be obvious, but I think people start muddying the waters -- especially when they focus on the ideological purity of their programming language. Programming languages are tools. The most popular ones are engineering tools -- and there are some not so popular ones that are tools for exploring the math behind the language itself. There are too many tradeoffs to have a language which occupies both spheres successfully.

In particular, the FP advocates go round and round on this issue. Just because something is elegant mathematically, does not mean it's good engineering practice. Haskell, for example, can be practical, but it struggles between the math and the reality of limited machines and human cognition[2]. Likewise, when you start talking about SML vs OCaml, you're talking engineering, not math -- and possibly a language tailored to engineering vs math.

You see this in languages like C++ too, where practicality starts giving way to a kind of semi-mathematical, yet totally non-scientific, dogma about how programs should be constructed based on their respective committee-designed[3] standard libraries.

[1] not always, but more than people really give others credit for.

[2] I still maintain Haskell is a write-only language, like an opposing pole to perl. Not (completely) because of the language itself, but because of the culture surrounding it which glorifies one-liner lambda calculus/laziness tricks over engineering pragmatics.

[3] e.g. compromises made in absence of any real on-the-ground engineering constraints.

I won't address all your points, but I think [1] deserves special attention: I think we can all agree that if you want to write software that will let you land a small vehicle on Mars, then you don't want/need the opinion of a theoretician, you just need $1B and a team of extremely disciplined programmers who will ADHERE TO PROCESS. Then you impose so much process that they either leave or prevail. What we're speculating about here (at least I think we are?) is if this is a sustainable model for general development and if we can do better. Even if we can't get better runtimes from FP languages, could we perhaps make programs which generate better C programs than those elite programmers that were chose for this particular mission? (I think we can. It has very little do with humans, but a lot to do with the fact that programs are very meta in that we can create programs that generate programs that generate programs ad-infinitum. If we can get our specifications right, the rest becomes trivial.)

A Mars lander program director explicitly said that he chose C because it was what he was familiar with. (I'll edit and post a link if I can find the video.) Just for context, his decision was also based solely on familiarity and experience. For him it wasn't quite so much about language, it was more about process (6 different industrial-strength linters, etc.)

> Just because something is elegant mathematically, does not mean it's good engineering practice. Haskell, for example, can be practical, but it struggles between the math and the reality of limited machines and human cognition[2]. Likewise, when you start talking about SML vs OCaml, you're talking engineering, not math -- and possibly a language tailored to engineering vs math.

That's the thing I would dispute, but it's hard to convince people who aren't already drinking the Kool-Aid, as it were. Compared to compiler-assisted reasoning about side-effects, the difference between SML and O'Caml is completely trivial.

Your [2] is just absurd :). Clearly, you don't have to understand the body/implementation of a function, just its type. :)

More seriously, I'd be interested if there's a particular experience that soured you on FP (or perhaps Haskell, in particular)...?

> laziness tricks

When your language is lazy by default, is it really a trick to take advantage of that?

Do you consider this a trick?

    take 1 [5..]
How about this Fibonacci definition?

    fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
What about this extensible fizzbuzz example?

    fizzBuzz i = if null desc then show i else desc 
        where desc = concat [label | (j,label) <- tags, 0 == rem i j] 
              tags = [ (3,"Fizz"), (5,"Buzz"), (7,"Baz") ]   
   
    main = mapM_ (putStrLn . fizzBuzz) [1..120]
Can I please quote your debating which particular brand of natural spring water should be given to millions of people who are thirsty in the desert when I get a chance :-)? It is a great summary of the problem with functional programming.

As a person with academic background interested in real-world FP (using F# in my case), this is exactly why I'm not very enthusiastic about most functional programming papers that appear in conferences like ICFP - they might be solving fun problems, but I'm not convinced they are problems that actually matter if we're going to ignore the main issue.

I think interesting functional libraries that demonstrate how to apply FP to some interesting problem can help here - for example, the paper on financial DSLs (http://research.microsoft.com/en-us/um/people/simonpj/papers...) or the original paper on Functional Reactive Programming (http://conal.net/papers/icfp97/).

No need to ask permission to quote me, but thanks for asking anyways. I definitely agree that shifting focus towards industrial use cases is what we need.
I guess it's more difficult than shifting the focus towards industrial use cases. Industrial use cases are a great thing, but it is something that the industry has to provide :-) (we tried to do something like that for F# here: http://manning.com/petricek2 (sorry for a shameless plug!)).

But many people contributing to functional programming (in one way or another) are in academia. They are not the best people to contribute industrial case studies - but I think there is still a lot to be done there too! The nice thing about the FRP paper is that it is really just a fun (and very simple and somewhat impractical) example, but it is nice inspiration showing (what was back then) a novel use of functional programming. Some of the more recent academic work around FP lacks this kind of creativity...

The financial DSL paper is a good example as although it was Haskell based, Jane Street eventually chose OCaml for production.
I agree that this was the situation a while ago. But, recently, I've seen very encouraging signs that the wider programming community is waking up to the value of FP. Languages like Scala and Clojure, whatever else you think about them, will hopefully act as gateway drugs.
Hi from "the sea". I think the Functional Programmers crowd overestimates how much better languages can really improve software development.

Projects in the industry would be massively improved by: more focus on quality (eg. decoupling), technical debt awareness, enhanced communication, enhanced architecture, internal engineer mobility, more thoughts given on social dynamics and productive work environments.

It pains me to say this but supposedly "bad" languages like C++/Java (which are actually really well done) are not the bottleneck for software development. It is only a distant factor among many that lead to software being the permanent tragedy it is in our era.

Agreed mostly, but I beg to differ with your conclusion that this makes the coice of language insignificant.

The point of modern languages (name them functional or not) is to erade whole classes of bugs. Also, it is about making it simple to define good interfaces between components.

So at least your points "focus on quality" and "enhanced architecture" are directly influenced by the programming language. Of course you should train your people to focus on quality, but you also shouldn't make it too hard to get things right in the first place. Of course you should train your people to develop a sense for good architechtures, but you should also provide a formal language that makes it more natural to express their architectural and design decisions.

That way, your training can focus 100% on the real issues, rather than 5% on the real issues and 95% on how to apply them in your programming language, as you need lots of workarounds, wrapper classes and so on. And you don't only have to write them - others also have to read that bunch of mess, and reduce this to the "real point" in their heads. This may be considered a nice mental exercise, but in the end, it's just boring, prone to errors/misunderstandings, and a finally waste of time.

> more focus on quality (eg. decoupling

> technical debt awareness

> enhanced architecture

I think all three of these can be improved and alleviated somewhat by better languages.

Some languages most certainly have cultures that promote properly taking care of all of these as central principles.

It's a bit shortsighted to think that since projects are hard for reasons other than language choices (too), we should just ignore languages all together.

At some point that'd just mean you end up realizing you could be even better with better tools, so why not start now even though everyone hates each other and can't talk to one another?

Toolchains also matter and OPAM (the package manager) has gone from strength to strength since this post. It's the basis for the OCaml Platform which combines a number of useful tools and libs into a coherent workflow (making development much more productive).
Also merlin is worth mentioning explicitly. It finds and highlights errors during editing (in emacs, vim and co), does autocompletion, shows types and since recently can also write pattern matches automatically.

http://the-lambda-church.github.io/merlin/destruct.ogv

OPAM still doesn't run natively on Windows though :(
I'm the author, and to be honest I agree. I was still fairly new to OCaml (and ML in general) at the time, so I tried to focus on what I knew rather than rattle off a list of things I had heard were better but didn't really understand.
Haskell has subtyping, it just doesn't use it a whole lot. That said, you can make arbitrary subtyping hierarchies using it if you like.
Actually Haskell doesn't have subtype polymorphism, at least not Haskell 98 (but maybe there is some new GHC extension nowadays that I'm missing - please elaborate if that is what you refer to).
It has width subtyping on type variable constraint sets. For instance, `forall a . a` is a subtype of `forall a. C a => a` and the compiler will automatically make the conversion by adding the unnecessary constraint. You also get the whole covariant/contravariance bit arising (solely) from function types.

This is a subtyping relation and you can treat it like one (and even abuse it quite a lot if you like) but since it's not key to think of it that way to understand Haskell's polymorphism then most of the literature just ignores it.

IMO key advantage OCaml is easier to learn and much more pragmatic.