Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Nice post! There are a lot of good reasons to use OCaml over Haskell which are more compelling than “JavaScript”, though. A few of them are:

1. modularity (and now that they have added generative functors à la SML, you can have true abstraction)

2. benign effects: in Haskell "proper", you do not have effects; rather you have "codes" for effects, which get interpreted into effects by the RTS; this rules out the possibility of, e.g., using effects to implement a semantically pure interface. On the other hand, OCaml has actual effects, which can be used in an open-ended way to implement all sorts of functional interfaces.

3. strictness: arguments abound about whether laziness or strictness is better; for me, it comes down to the fact that with some pain, you can embed laziness in a strict language with effects, but you cannot embed full-on ML-style strictness into a language like Haskell; moreover, strictness-by-default permits safe uses of benign effects.

I'd call Haskell an expression-oriented programming language, since the types end up classifying expressions and algorithms (i.e. the particular "effects" you used get put into the type). Whereas I'd say (OCa)ML is a value-oriented language, since values (canonical forms) are considered separately from general expressions (canonical and non-canonical forms); moreover, implementation details don't end up in the types, so you can really consider them to be classifying values and functions (i.e. equivalence classes of algorithms, not algorithms themselves). This is largely orthogonal from strictness vs laziness, but as soon as you add partiality in, strictness becomes the only tractable way to have canonical-form-based meaning explanations for the judgements of the theory.

P.S. My day job is writing Haskell. (In case the Pedagogical Brethren wish to come and "correct" me.)



Adding my own two cents:

- I like that with OCaml you can choose if you want your code to be functional or imperative, and both are equally well-supported. I am writing a compiler in OCaml, and I am able to use the API described in Peyton-Jones & Lester for doing pretty printing while still using imperative algorithms from the Dragon Book. This sometimes feels like cheating, but not having to figure out how to transform imperative code into functional code while still maintaining the same complexity constraints is really nice.

- OCaml code is usually a lot more "boring" than Haskell code, which is nice when reading other people's code. Let me explain: I am T.A.'ing an intro to compiler class where some students use Haskell and some use OCaml. The Haskell styles of the students varies a lot: some prefer to use point-free style, some used Parsec others AttoParsec, some passed state around manually while other use a state monad, etc. The OCaml submissions on the other hand were a lot more homogeneous: ocamllex for the scanner, menhir for the parser, the AST definition was almost identical to mine, and the code generation was also very similar.

- This may just be me, but I have had less problems with opam than with cabal. Also, the merlin tool and its integration with Emacs are really good and give me the kind of minimal, out-of-my-way IDE experience that I am looking for.


I generally prefer programming with immutability, but I certainly appreciate the ability to use "benign effects" in my programs. Besides the case for performance, I have applied a strange combination of benign effects, GADTs, and functors to generate a sort of "proof of type equality" between two values passed into a framework, each of which are not known to the framework because they're passed into the framework by two separate clients of the framework. At that point, I had the ability to reason about two values with arbitrary and potentially distinct types, as either being not the same (None), or the same Some(x, y) with x and y having the same type.

I have no clue if there's a more elegant way to do this (edit: there probably is), but even I (as a n00b) was able to figure out how to do this by using benign effects. There's only a single mutation in this library - but it was such a critical one that made everything else possible.

I'm curious about the reason for preferring generative functors over applicative functors. It seems like both could have valid use cases. Could you point me to a writeup that explains why you believe generative functors are superior?


After numerous discussions with Bob Harper (and reading Derek Dreyer's work, who has been the biggest proponent for applicative functors), I have come to understand that there are only two compelling use-cases for applicative functors:

1. higher order functors.

2. modular type classes

Higher order functors are kind of cool, but IMO Standard ML does not seem to be suffering too much from the lack of them. I'm not too interested in it, since it gets super gnarly super fast, and this happens to be pretty much the main use-case for applicative functors. I suspect that most use-cases of higher order functors in OCaml could be reformulated to be first order, with a lot less monkeying around in the module system. There may be compelling use-cases though.

The other use-case is possibly a version of modular type classes that behaved a bit more like Haskell's. The idea is that if functors are going to be applied automatically during elaboration to provide something like type classes, you'll get MkWelp(S) called in multiple places, and you would prefer that any type members of the resulting structure be compatible. Applicative functors would do this.

I am not too convinced by this use-case, though I could see that people would find it useful.

In all other cases, generative functors have the correct semantics. Pretty much the whole use-case of putting abstract types in a functor is that you can then reason intensionally about them (i.e. distinguish them based on their access path). This is super useful, for instance, if you have a notion of "index" or something and you want to prevent yourself from using indexes from one table in another one, or something along those lines.

This is what the Scala people mystifyingly call "path dependent types". It's just generative abstraction.

So maybe it is an interesting feature to have applicative functors, but these should be added post facto, and generative functors should be the default. OCaml now supports generative functors if you add an extra () parameter; it's strange syntax, but it's good enough for me! :)


I think I disagree with this. Your functors should generally be pure, and for pure functors applicative is a nicer semantics.

Consider the case of a set implemented as a binary tree. The type of such a set should be parametrised by the type of the elements and the ordering used. With applicative functors this is the case as your set type will be `Set(O).t` where `O` is a structure containing the type and the ordering. With generative functors each individual set type is abstract -- so the type itself is not parameterised by the ordering.

You could consider this to be just an example of what you are calling "Modular Type Classes", but there doesn't need to be a system of implicit module parameters for it to be useful.


lps25:

> Consider the case of a set implemented as a binary tree. The type of such a set should be parametrised by the type of the elements and the ordering used. With applicative functors this is the case as your set type will be `Set(O).t` where `O` is a structure containing the type and the ordering. With generative functors each individual set type is abstract -- so the type itself is not parameterised by the ordering.

You make a good point about this not being strictly about type classes. But I'd say that the issue is only a problem in the presence of implicit resolution, since otherwise, you can just bind the result of the functor to a structure once and be done with it. It becomes an issue with type classes, because you don't have the choice to share a single structure during elaboration.

IMO, the generative version is still better for most use-cases (pure or not), but it's nice that you can have both in OCaml.


>> since otherwise, you can just bind the result of the functor to a structure once..

My understanding was that of lpw25's. I wanted to use applicative functors in the same way type classes (or OCaml's modular implicits) would make use of them, but even without type class's implicit resolution (explicitly specifying them). I agree there isn't too much difference from doing the same with generative functors, but the main difference is, as you said, I would need to find a place (some appropriately accessible location in the namespace) by which to access the result of these generative functor applications, and that's just an extra point of friction (if I understand correctly). It's not the end of the world as you said either way because we can do either.

Thanks for the help, jonsterling/lpw25.


The first chapter of Dreyer's thesis "Understanding and Evolving the ML Module System" (http://www.mpi-sws.org/~dreyer/thesis/main.pdf) talks about generativity


> 3. strictness: arguments abound about whether laziness or strictness is better; for me, it comes down to the fact that with some pain, you can embed laziness in a strict language with effects, but you cannot embed full-on ML-style strictness into a language like Haskell; moreover, strictness-by-default permits safe uses of benign effects.

I've seen this claimed before, but I'm not sure I can subscribe to any sense in which this statement is true. It's been known since at least John Reynolds that you can make programs evaluation order oblivious by means of a CPS transformation: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.110..... Just as thunkification is a program transformation to simulate call-by-name, the call-by-value CPS program transformation is a way to simulate call-by-value.

Moreover, Haskell has strictness annotations, so strict programming is possible if you pepper all the arguments of all your functions with strictness annotations and you use only strict datatypes. In fact in the future a language pragma will do this for you: https://ghc.haskell.org/trac/ghc/wiki/StrictPragma. That's less invasive than a CPS transformation.


Nope! You may be able to get evaluation order out of it, but the main reason I care about strictness is not CBV evaluation order. It is the following:

1. Reasoning by induction: not possible with a lazy function space

2. Non-pointed types: also not possible in Haskell

3. Compatibility with a proper treatment of effects

It's great that you can do strictness annotations in Haskell! But they don't accord one the above facilities at all.


> 2. benign effects: in Haskell "proper", you do not have effects; rather you have "codes" for effects, which get interpreted into effects by the RTS; this rules out the possibility of, e.g., using effects to implement a semantically pure interface. On the other hand, OCaml has actual effects, which can be used in an open-ended way to implement all sorts of functional interfaces.

So, to be all pedantic and stuff... You've always got unsafePerformIO, first off. This is used in Debug.Trace (https://hackage.haskell.org/package/base-4.7.0.2/docs/src/De...) to allow printf debugging in pure code. This is also used in some Haskell libraries to provide restricted effects in monads other than IO. But if you don't want to use unsafe* functions, you can always use ST to get direct access to mutable memory in a safe way. If you can runST within that interface, then you can present a pure interface to users.


1. that's why I qualified it "proper". Of course there is unsafePerformIO, but in the presence of laziness, this is really unsafe. as opposed to ML, where doing IO may harm your ability to reason about stuff, but it won't be unsafe.

Debug.Trace is also unpredictable; actually, it's perfectly predictable if you understand laziness, but it's still not what's really wanted in most cases.

2. ST is a single instance of an effect that can be interpreted into "pure" code. There are plenty of other effects that don't work like this in Haskell, not to mention the problem of composing them together.

So thanks for chiming in! But what you have said is not really that different from what I have said.


I would love to talk to you a little about these kinds of things. I've been digging more seriously into OCaml the last few weeks and would love some signposts for making the Haskell->OCaml transition.


Hi! Please feel free to email me at any time. I can't promise that I will know everything you want to know (since I only know very little), but I would be happy to help with what I do know. jon [at] jonmsterling [dot] com.

BTW, I looked at your OCaml stuff yesterday, and it seemed pretty cool.


Could you say something about why `unsafePerformIO` is unsafe in the presence of laziness?

I know `unsafePerformIO` can be used to violate the type system in combination with `IORef`s, for example, but I don't see what laziness has to do with it.


In the presence of laziness, it can be very difficult to reason about what code executes when. When you don't know when your unsafe IO happens, things can get out of order or have other unforeseen side effects.


With regards to 2., could you briefly mention some other instances? I have no idea what I should be thinking of.


Thanks! (I'm the author). I focussed on JavaScript because it's what I know, it's my day job, and I wanted to share a bit of my research while trying to introduce AltJS to it.

I wasn't (and am still not) really enough of an expert in either Haskell or OCaml to talk about other differences; including a bunch of dot points I don't understand wouldn't help anyone :)


Hi! Thanks for writing the post. I didn't mean to criticize you for talking about what you know; I hope I was able to help give a broader perspective on some of the other issues at hand too. :)


>1. modularity (and now that they have added generative functors à la SML, you can have true abstraction)

Could you share some information regarding Haskell and it's 'modularity' problem (vs. the ML family of languages). I'm fond of the way SML projects can be structured. How are people solving this using Haskell? Are there any interesting solutions for creating modular Haskell application/system's I can see today?

Thanks for your comment.


There is a weak form of modularity which can be achieved by using two parts of haskell:

1. hiding constructors in files 2. type classes

But it doesn't really begin to approach the kind of structuring that is possible in an ML-like system. Unfortunately, the issue is very (needlessly) controversial, and I don't think I want to get dragged into it here.

I'll mention, though, that Haskell does have one form of modularity which ML doesn't really, which is the fact that you can write algorithms separately, compose them together after the fact, and expect to get reasonable performance in most cases. This is because of two things: haskell is non-strict, and GHC has pretty good fusion. In ML, you often end up manually fusing things together in order to get good performance, so composition can be a bit more difficult.


> I'll mention, though, that Haskell does have one form of modularity which ML doesn't really, which is the fact that you can write algorithms separately, compose them together after the fact, and expect to get reasonable performance in most cases.

I'd actually argue the opposite. MLton is one of the best whole program optimizing compilers I have ever used. There are virtually no penalties for abstraction. OCaml has a bit of trouble here, from what I've heard, but I've never personally run into serious performance problems as a result of abstraction.


sgeisenh — wow! How cool. If that is the case, than that's awesome, and makes me very happy.


Awesome response, but....

> (In case the Pedagogical Brethren wish to come and "correct" me.)

Seems very unnecessary and is a big departure from the Haskell community I'm personally used to. On top of that, it's not the tone I'm used to on HN for the most part either.

Apologies for going off topic, just felt obliged to chime in.


Hey, sorry... You're right, I shouldn't have said this. Over the past few days, certain parts of the Haskell community have been particularly vicious (toward me and others), and I have been feeling a bit beleaguered. I shouldn't have brought it here though. Cheers.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: