Why OCaml, why now? (2014)
spyder.wordpress.com
spyder.wordpress.com
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.)
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?
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! :)
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.
> 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.
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.
- 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.
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.
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'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.
> (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.
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 :)
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.
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.
BTW, I looked at your OCaml stuff yesterday, and it seemed pretty cool.
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.
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.
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.
> The answer is JavaScript.
My current favorite language for this is Elm[1]. It compiles to JS, is based on Haskell, and has a few simple differences between either[2].
Elm doesn't let you use the tons of available Haskell libraries, and makes it slightly painful to integrate with JS. But it's the coolest thing for the web so far. And here's why:
It uses a completely declarative state machine to model a fully interactive and complete GUI! This is the dream React.js and Om didn't even know they were heading towards! And yes, it is as good as it sounds.
[1]: http://elm-lang.org/
[2]: http://elm-lang.org/learn/FAQ.elm
EDIT:
Also, Elm fixed Haskell's awful syntax record name collision problem, and awesomely too!
It has some changes however; most notably it's strictly evaluated rather than lazy, and there are differences in how it handles type variables. [3]
Personally though, I've grown more fond of F# than Haskell; I only wish the documentation for Websharper and Funscript was more solid.
[1] http://www.purescript.org/ [2] https://leanpub.com/purescript/read [3] https://github.com/purescript/purescript/wiki/Differences-fr...
1. an language very similar to Haskell
2. a set of runtime libraries
3. a technique for modeling a GUI as a declarative state machine
PureScript only competes with #1 and #2 here. Where Elm really shines is #3, which #2 helps with a lot.
Technically #3 could be done in any language. But it really helps to model it in such an expressive language with immutable data and no side-effects (except via Signals).
https://github.com/paf31/purescript-thermite
https://github.com/kRITZCREEK/reactive-psc
https://github.com/bodil/purescript-signal
https://github.com/michaelficarra/purescript-demo-mario
https://github.com/mechairoi/purescript-frp-rabbit
To me, this is the benefit of PureScript - yes, you have to do a little more work, because you don't get these things for free from the compiler and tools, but you gain complete control over your application structure. You're not forced to work in some ambient Signal category.
TLDR: he thinks elm's the most practical approach right now.
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.
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)
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.
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.
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. :)
- 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.
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.
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)
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!
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 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.
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.
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)...?
Here is the kind of difficulty I'm talking about. Instead of thinking about this on a continuum from Aeronautics & medical (where people could die) to yet another throwaway web TODO app (e.g. "general dev"), there is this tendency to say that they are "completely different" in some way. Let's be clear, the theories do help both. Side-effect free programming is useful. But you cannot take theory and just map it directly to the real world with no caveats. Just because Haskell tracks side-effects, doesn't make it superior to C in every context.
The Mars Rover shows this clearly -- C was chosen because someone was used to it, yes. But the part you didn't catch was the implicit decision that it makes no sense to use a compiler and ecosystem you don't understand the caveats to when you need to understand all the caveats to build a successful system.
Similarly, the TODO app developer isn't using Haskell because Haskell doesn't have anywhere near the libraries Python or even Go does, and building and deploying Haskell programs is somewhat of a chore compared to those. It's no contest, Go is superior to Haskell. :-) It's also one of the reasons almost anything is superior to C/C++ in this very same domain. :-) It's not about being "entrenched", because Go is way, way younger than haskell -- it's about the priorities the language designers and developers for that language have -- ie. the culture.
> If we can get our specifications right, the rest becomes trivial.
This is what I mean about theory and practice. Note the big "if" there. I totally understand the sentiment, and I wish it were true. I even have my own meta-programming based language in the wings I'd like to release some day.
But the reason I've stalled a bit is I've never seen this work in practice because our minds (and specs) tend to paper over the devilish details. I'm not saying we shouldn't use meta-programming, I'm saying that it should not become dogma. Invariably, you get caught up in details. This kind of domain specific "meta-programming" is a great bootstrap technique, but doesn't appear to be "the way" programs should be written.
The OMeta folks created a TCP stack that compiles (almost) directly from the specification. But that was a academic exercise. How many special cases do you think they cover? Does anyone really believe that the stack in question is anything but a way to bootstrap a more robust/performant implementation later on?
Similar for the PyPy folks. Yes, you can JIT compile a python interpreter, but how many years have they worked on special cases for that, and how much farther would they have gone if they hadn't used the meta-circular approach and just addressed the real problem to begin with[1]?
> Compared to compiler-assisted reasoning about side-effects, the difference between SML and O'Caml is completely trivial.
I think as a general statement, this is true, but is a poor way of thinking about things. You're not comparing OCaml to SML, you're comparing it to C++ or Java. If all the language brings is side-effect reasoning, it's not enough, because I can add decorators to C++ code to do what you're asking.
Even with "side-effect handling" these languages don't assist you with all of the side-effects someone actually cares about. Is there a decoration for runtime speed, for memory consumption, not just for IO, but for the amount of IO? Is it even predictable? These are the real things people care about, not just whether a function peers into some global state somewhere (although that is an important thing to track, it isn't the biggest issue, IMO).
> Your [2] is just absurd :). Clearly, you don't have to understand the body/implementation of a function, just its type :)
Is this sarcastic? :-) I mean, anyone with a reasonable amount of experience knows that this is not true most of the time.
It's not completely false -- one doesn't always have to look at the implementation of the operating system facilities or even the standard library. But in code that is less than tangential to what you're working on, you certainly do end up having to understand how it's implemented. As a developer, you spend more time reading code others wrote than writing it.
> More seriously, I'd be interested if there's a particular experience that soured you on FP (or perhaps Haskell, in particular)...?
I'm not soured on FP as a concept (which could mean many things, but I just mean state-awareness), just haskell. The <space> fn call operator combined with currying in particular seems like an advancement to rubyists[2] and those think succinctness is a virtue above all others[4], but it is an engineering disaster -- completely unreadable at the call site unless you know the arity of every function by heart.[3]
But maybe I've just read bad code... I'm not dismissing that possibility. :-)
[1] The problem with python was never optimizing plain python (look at JS as an example) or the "GIL", the problem was (stupid) performance requirements about the GIL from guido, and later on, C extension API compatibility.
[2] Do not get me started on the "domain specific language" shit-fest that ruby (and progenitors like groovy/gradle) have unleashed on the world. At least stack overflow gets money and page views from it, I guess.
[3] As much as people like to put down smalltalk (cum obj-c) keyword argument syntax, it is a revelation when you're maintaining code (and I'm sorry Swift seems likely to drop it as a default).
[4] Is Arc (PG's lisp) used anywhere significant, but for this website?
It takes longer to write but makes reading/understanding much faster. When you write the code it is obvious in any language what your calls "mean", but no so obvious 3 months later.
Writing without keyword syntax is as if all our emails just referred to "him" and "her" and "they" and "there" never mentioning the proper nouns of who or what or where are we actually talking about. It is obvious everybody understands what we are saying at the time those emails are written. But for another person or you yourself trying to understand what a specific email is actually saying would be rather difficult. Relying on the POSITION of an argument, rather than the name of it in the calling context is like saying "that argument which is the 3rd".
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]Yes, these are tricks, because "lazy" means you're storing thunks and significant partial results with no outward indication of such. Just because you don't see it, doesn't mean it's not there. I could say the same about C's allowance of global state, or spurious use of recursion without a depth guard.
To your examples:
A trick, because who would ask for a constant from an infinite list that could be generated numerically in a fraction of the time?
A trick, because for random queries of large n, this is not the optimal way to compute it (the optimal way is more complex). It also entangles memory allocation and the computation. Note how haskell is "side-effect free" for fibs, yet somehow memory allocation or the time required for allocation is not considered a side-effect when I ask for "fibs !! 2000000". The C iterative solution is trivial, and will execute faster.
A trick, because no one cares about fizz-buzz being extensible, and this is not particularly clear to the reader compared to an ordinary if/switch/case expression.
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/).
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...
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.
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.
> 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?
I can speak from what I observed here at Amazon... which did try and make use of Erlang (due to its excellent concurrency model) at one point, but gave up (Simple DB is powered by Erlang, but its successor DynamoDB is JVM based). At least Erlang had its chance... OCaml community within Amazon is non-existent. Whereas Clojure (plenty of traction within due to STM on JVM, I guess) has plenty going for it, as does Nodejs and go-lang. All of these plaforms are popular due to the strong community presence both within the company and at other tech power houses.
I'd also like to point out that the current crop of volunteers behind ocaml.org [1] are doing a really great job of evangelising OCaml.
[0] https://ocaml.org/learn/companies.html and http://clojure.org/Companies and https://wiki.haskell.org/Haskell_in_industry
Edit: seems you've added the links I was about to point to.
That's definitely a large factor. But let's not forget that Go already took off when it was barely beyond being a 20% project. Go also fills some niches, three particular ones I can think of are:
- People who like Java, but favor the UNIX approach of small programs over the JVM.
- People who like Python or Ruby, but need more performance.
- People who like C, but want garbage collection and some extra safety in some of their projects.
Add to that that Go is trivial to learn for anyone with a Java, C, or C++ background. Of course, it takes some time to learn all the idioms, but someone who knows languages with C-like syntax can start writing Go programs productively within a day.
Ah, that must be why the golang toolchain does not support bloated enterprise features like dynamic linking, so a hello world binary ends up almost 2MB. Go UNIX!
Compare these two Stack Overflow "getting started with" answers for Haskell http://stackoverflow.com/a/1016986 and F# http://stackoverflow.com/a/11974625 With the Haskell answers I don't know or care what platforms, operating systems, editors, or IDEs they are using. With the Microsoft answer it's like a dystopian scene filled with Branding, Branding, and more Branding.
Personally, I spend a lot of time experimenting with languages and ecosystems and the ".NET" platform is the only one where I feel the need to put on a plaid shirt and dockers with a Windows Phone on my belt and schedule a conference call with a committee at Microsoft to tell them to give it a rest already.
brew install mono
run f# console/repl: fsharpi
compile files: fsharpc file.fs
Not sure about linux, but should be simple as well.
And as with Haskell and cabal, you have FAKE ( http://fsharp.github.io/FAKE/ ) which isn't exactly the same but similar
The main difference between those two answers is that for Haskell, the author pointed to generic Haskell resources while F# pointed to windows ones. I've been developing with F# for a while on the Mac and found no issues whatsoever. There is also MonoDevelop/Xamarin if you want a more complete IDE which is better than anything I found for Haskell (but there maybe new stuff I'm missing, having messed with Haskell in a while) but SublimeText and Vim work fine.
Plus, sane typeclasses and higher-kinded types. :-)
I can understand not choosing to go the ML route "down the Functor rat hole" as one of its developers said. But then it also chooses not to do the simpler type classes either. Apparently a proper impl would need CLR changes which is a downside to the reified generics, they are baked in as C#/VB imagined them. That leaves a language that conceptually is very close to C#/VB, with a somewhat different syntax.
I find either of typeclasses or an ML-like module system far preferable for expressing abstractions than traditional ("left-biased") OO, even if OO can (clumsily) get the same results in other ways.
Or I may have missed some developments in F# since I last looked. I really hope so, F# does have a lot going for it.
But it doesn't really matter. Some pieces might be slightly more verbose, but Scala is more concise in the long run, not because of saving some syntactic stuff here and there, but due to being able to express things which just aren't expressible in OCaml or F#.
If you want some shorter, more ML-like syntax, just pick a different parser https://github.com/lihaoyi/Scalite and have fun.
B: Unlike in OCaml/F#, OO is not "baggage". Scala took the good parts of OO and made them even better. OCaml/F# feel like the language creators tried to make a point about disliking OO by making OO support terrible on purpose. Scala demonstrates that having good FP support doesn't mean OO support need to be terrible.
Some of the stuff for Java support is a bit annoying, though, but doesn't matter in practice.
C: Performance is on par with Java. Scala can leverage the most powerful JIT-compilers and the world's best Garbage Collectors known to man. Does OCaml have working support for concurrency yet?
Also, when your programs use abstract data types you lose the benefits of pattern matching. In that case, I'm happier with languages like Go or Ada with a friendlier syntax.
For instance, here we have Option
module Option = struct
let scott (some : 'a -> 'r)
(none : 'r)
(opt : 'a option) =
match opt with
| Some a -> some a
| None -> none
end
For Option, since it's non-recursive, the Scott Encoding and the recursor/inductor/Church Encoding are identical. Here's a linked list, though module LL : sig
type 'a t
val fold : ('a -> 'r -> 'r) -> 'r -> ('a t -> 'r)
val scott : ('a -> 'a t -> 'r) -> 'r -> ('a t -> 'r)
end = struct
type 'a t = Cons of 'a * 'a t | Nil
let rec fold cons nil = function
| Cons (h, t) -> cons h (fold cons nil t)
| Nil -> nil
let scott cons nil = function
| Cons (h, t) -> cons h t
| Nil -> nil
end
Anyway, the pattern should be more clear now. These provide effectively "functionalized" pattern matching which you can apply whenever you need. In particular, you can think of these as expressing a (potentially partial) "view" of the abstract type. For instance, my linked list might have not been a linked list exactly but instead some kind of tree, but `scott` and `fold` let me expose a "view" of that tree as though it were a linked list.Inheritance and open-recursion/dynamic binding are usually what remain. I'm not terribly sure I miss them.
For example, see this section of Real World OCaml:
https://realworldocaml.org/v1/en/html/classes.html#open-recu...
In some cases you can also use "private ADTs". Modules seeing such a type can use pattern matching on them but can not apply the constructors.
I especially don't understand how people get tripped up by laziness. 99.9% of strict code will work with no changes in a lazy environment. The rest usually just needs a slight tweak to avoid memory leaks.
To anyone considering undertaking the modest effort required to learn Haskell, I completely recommend it. My code in every language has tangibly improved.
For better or worse, switching to Haskell does entail asking yourself how hard it'll be for your team to become comfortable with things like http://learnyouahaskell.com/functors-applicative-functors-an...
Learning Haskell may well be easier than using it, although the same is true for most languages.
http://packdeps.haskellers.com/reverse/lens
(Yes, I am guilty too, but I inherited the dependency via Chart ;).)
First, you may be unusually quick, gifted, clever -- whatever you might want to call it. In this case, the new concepts in functional programming might come easy for you. That's great, but to people for whom they don't come so easily, this could easily be considered discouraging, or else bragging.
Second, consider that maybe you haven't advanced as far in learning Haskell as you think you have. In my estimation Haskell can take you up an abstraction ramp, that has no clear and obvious "end." As a research language, higher up that curve is some really mind-blowing stuff. So unless you're SPJ or beyond, I wouldn't be quick to claim that Haskell isn't "unusually hard to learn."
Third, you may be a stage in your life where you can put a lot of free time into learning, but recognize that not everyone is there. In my twenties, I worked much more than full time, and in my spare time worked on yet more programming, teaching myself other languages and environments. Really in retrospect it's fortunate I didn't damage my physical or mental health more than I did. But not everyone is in that boat now. In particular people who are older and have "work/life balance," who are professionals in some form of software development, may find that their employment doesn't offer them much in the way of opportunities to learn another language, and the life part of that work/life balance doesn't allow much time to do so.
Me, I'm somewhere partway up that ramp and proud of how far I've come (I feel like I can use at least simple monads now) but still very aware that there is a lot I don't fully grok (currently trying to get my mind around arrows). And I agree, I completely recommend it, but just maybe don't be so glib to claim it is not hard.
No, but hopefully it well help people who have yet to learn Haskell that not everyone thinks it's particularly difficult. I'm not interested in convincing the people who've tried and given up on it.
>That's great, but to people for whom they don't come so easily, this could easily be considered discouraging, or else bragging.
So what do you propose? That I pretend it's really hard for me, even though it's not? What effect do you think that's going to have?
>So unless you're SPJ or beyond, I wouldn't be quick to claim that Haskell isn't "unusually hard to learn."
You don't have to be SPJ to learn Haskell (or talk about how hard it is). Do I have to be Stroustrup to pass judgement on how hard it is to learn C++?
>In particular people who are older and have "work/life balance," ... and the life part of that work/life balance doesn't allow much time to do so.
I obviously don't mean that Haskell is easy for every single person in the world to learn; I mean that, as far as languages go, it's relatively easy enough to learn. If I didn't have enough time to learn Haskell, I also wouldn't have enough time to learn C++ or Python.
> but just maybe don't be so glib to claim it is not hard.
Again, what do you propose? Lie and tell people that it's super hard, so I don't hurt their feelings if they can't figure it out?
Your attitude is one I see too often in the FP community, and well-meaning or not it holds us all back.
Again, what do you propose I do about it? Should I never say anything positive about anything, lest I offend someone who had a bad experience?
>Your attitude is one I see too often in the FP community
Which attitude is that? Optimism?
Well if you can't understand why your phrasing turns a positive idea into making somebody feel bad, probably.
> Which attitude is that? Optimism?
A version of it, yes. The "if you don't find it easy you are the problem" attitude.
Maybe a more constructive (humble?) approach would be to show others why it was easy for you, subjective as that may be, and how they can achieve faster comprehension levels based on your own experiences.
Haskell aims to be a terse language, right? That's one reason that makes it difficult to "read" it and if you can't read it it's hard to learn it.
Think about learning to read and write Chinese when all you know is English. The only way to do it is to have a text-book that shows you sentences in both English and Chinese. I don't think it is necessarily "difficult" to learn Chinese, but you need learning materials targeted to an English-speaker.
If (when) I ever learn enough to talk about why OCaml is better in detail, it will be with concrete examples not buzzword bingo :)
Again, what do you propose? Lie and tell people that it's super hard, so I don't hurt their feelings if they can't figure it out?
I don't anybody is suggesting lying about your own personal experience with Haskell. Rather your posts state that it was easy for you and that it should be relatively easy for everyone.
I think that when programmers from other languages are having trouble figuring out how to write a particular program in Haskell, whether it be due to documentation, the behavior of the code execution, or even language syntax, it doesn't make sense to claim that it should be relatively easy for them.
In my (limited) experience with Haskell, there can be hang-ups that someone with experience in strictly-evaluated languages isn't expecting. [1]
[1] https://wiki.haskell.org/Iteratee_I/O#The_problem_with_lazy_...
It's more akin to studying than anything else. I've had evenings where I've been unable to proceed because I can't write one line. It's OK; it's part of learning. No one said it would be easy.
Beginner's mind is key.
Regarding the second, I don't think it makes sense to consider "difficulty in learning Haskell" to be "difficulty in learning all of everything anyone's done with Haskell." Even SPJ has (or at least recently had) a pretty shallow understanding of Kmett's lens library. The two important questions are 1) how much do I need to learn to get things done in Haskell, and 2) how much do I need to learn to participate usefully in the Haskell community. 1 is less than 2, which is less than what you described.
"There is more stuff to learn that can make you even more productive, but you can get by without it and still be productive" often gets treated as a negative, and I find that strange...
My opinion now is that if you're forced to use the JVM then Scala is one of the best options. But that's a low bar, given the choice I would pick something else.
Modular implicits: https://github.com/ocamllabs/ocaml-modular-implicits