1. 35

  2. 11

    It reminds me of The Haskell Pyramid: several (maybe even most?) of the benefits of programming in Haskell come from basic constructs.

    Hail Occam’s razor, Pareto principle, KISS principle and similar reasonings, once again, in a world full of (essential, but mostly incidental) complexities. 🙂

    1. 1

      Thanks for sharing that… maybe I’ll give haskell a try after all!

    2. 4

      I wouldn’t be surprised if the boring Haskell language subset would probably be a language pretty close to SML, but pure.

      1. 6

        and, sadly, lazy.

        1. 7

          I get upset when people talk about laziness strictly as a bad thing, while for me it’s almost purely a good thing. In my 3 years of full-time commercial Haskell development, I’ve used laziness countless times to simplify my code greatly and never have I been bitten by it performance wise. I have spent zero seconds debugging strictness issues.

          1. 7

            Fair enough, I was unecessarily dismissive. I personally find lazyness hard to reason about, and I think it requires heroic efforts from the compiler to get good performance. OCaml gets you pretty fast programs with very little optimizations in comparison. But indeed, there’s a lot of elegance in lazy functions.

            1. 5

              I do sometimes talk about laziness as a bad thing but I have a specific argument in mind when I do this: eager languages are more expressive in that they can express lazy structures whereas lazy languages cannot express eager structures. There is a fundamental duality between data versus co-data in computer science: the former is about inductively building bigger structures out of smaller ones whereas the latter is about (co-inductively) building smaller things out of (infinitely) big things.

              In Haskell, one works with co-data and pretends that it is data. For instance: a natural number by its very definition cannot be infinite: any inhabitant of the type ℕ must be a natural number you can write down; it should be a finite thing made up from gradually smaller things. Consider the following definition

              ∞ : ℕ
              ∞ = ∞ + 1

              In both Haskell and SML you can write something like this but an attempt to evaluate it should get the program immediately stuck in an infinite loop. In Haskell, however, this is not the case because it is treated as co-data due to laziness. So ℕ really behaves like co-ℕ, the dual of ℕ. The situation is the same for lists: you can never write the type of lists in Haskell, only co-lists (i.e., streams) (of course here, I am hand-waving over the extremely important question of what one means by equality; by a suitable definition of equality you can look at them as the same thing).

              There exists a fundamentally co-inductive type in almost every language: the function type. It is built into the language and its inhabitants are inherently co-data rather than data. Using function types you can express many other co-data: for instance the type of lists over some type A in Haskell can be represented by a type like ℕ → A + Unit in SML; the idea is that this is like a list you can force into existence as you observe it which is the meaning Haskell assigns to ordinary lists.

              Agda for instance allows the user to express their precise expectations from a type as it supports co-inductive types so in that sense it is superior to both. It also supports co-pattern matching: the dual of pattern matching, an extremely convenient tool for dealing with co-data. If we are to choose between SML and Haskell, though, there is a sense in which SML is superior.

              Check this article out if you are interested in reading more: https://existentialtype.wordpress.com/2011/04/24/the-real-point-of-laziness/.

              1. 3

                What are the strictness issues that are hard to debug? All cases I can think of (forgetting that rectypes isn’t the default, or forgetting to thunk something) blow up fast, usually at compile time, and are fairly straightforward. What am I missing?

            2. 3

              In addition to purity, the most obvious differences that come to mind are:

              • laziness (I like it, like enobayram)
              • typeclasses vs. modules
              • higher-kinded types (although sounds like modules allow for an approximation?)

              I’d maybe also toss in generics as a great feature that doesn’t get as much press, although I think you need DeriveGeneric to really leverage the full power there.

              Taken as a whole, my impression is that these lead to a pretty different programming style compared to ML-descended languages, but I don’t have any experience with ML-family languages so can’t really say.

              1. 15

                Having used both ML and Haskell, I can guarantee that the programming styles they encourage are very different.

                ML encourages you to use types to describe states. If you have a long computation, you define a data type for each intermediate state, and a function for each computation step. To ensure that steps may only be chained in ways that make sense, you use abstract data types. The hallmark of good ML programming is designing abstract data types in such a way that third parties may not create invalid values.

                Haskell encourages you to use types to describe computations. If you identify a pattern that several computations follow, you formalize the pattern as a higher-order function parameterized by the varying parts, which may be either explicit function arguments or implicit type class dictionaries. To ensure that your patterns are reasonably universal, you demand that they satisfy laws that can be stated as equations. The hallmark of good Haskell programming is identifying the most generally useful patterns that computations follow.

            3. 3

              Perhaps “boring” is the concept sought elsewhere - no frills, no surprises, nothing new; just a well-understood, battle-tested subset of ideas that get 80% of the results with 20% of the complexity.

              1. 4

                Does it count as ‘boring Haskell’ if it starts by enabling 39 language extensions?

                1. 3

                  Did you look at the list? It’s almost entirely syntax sugar extensions.

                  1. 1

                    GADTs, existential quantification, multiparameter typeclasses, polykinds, rankntypes, scopedtypevariables, type families and datakinds are hardly syntactic sugar.

                    I think enabling stuff like ViewPatterns is fine. That is syntactic sugar. RankNTypes is not.

                  2. 1

                    They start with 39 extensions in their proposed standard library, by my hand count.

                    “Boring” seems like a carefully-chosen word to distract from the obvious commercialization being attempted by the author.

                    1. 4

                      Distract? We’re trying to get paid to move more companies into using Haskell. That’s my (I’m Chris) job and the whole thesis of the article is that there is a highly productive subset of Haskell that is extremely useful in software projects operating under commercial constraints.

                      The first line is:

                      Goal: how to get Haskell into your organization, and how to make your organization more productive and profitable with better engineering.

                      Your reply isn’t constructive and casts aspersions by claiming the explicit point of article is somehow an act of subterfuge. We just want people to start using better tools. For us programmers at FP Complete the reasons for that are selfish but straight-forward: we’re programmers who want to use the tools we like because they make our work less tedious.

                      I want to get paid to write software in nice programming languages. I want to create more jobs where people get to get paid to write code using nice programming languages.

                      1. 1

                        The ‘highly productive subset of Haskell that is extremely useful in software projects operating under commercial constraints’ does not involve starting with 39 language extensions and a huge pile of extremely complex type system nonsense that results in awful error messages. If people want to use a language with cryptic type errors and high performance they should use C++.

                        Haskell’s highly productive subset is Haskell with at most about 3 language extensions, all of which are totally optional, along with a set of well-built libraries. I’d avoid typeclasses entirely, and even if you don’t go that far, certainly I’d avoid GADTs, lenses of any kind, and anything to do with the word ‘monad’. No MTL or anything of that nature.

                      2. 3

                        I’m confused, is commercialization a good thing here or a bad thing?

                        1. 1

                          39 extensions in their proposed standard library

                          That’s what I meant, whoops.

                          1. 3

                            A lot of language extensions are innocuous and useful. It’s not our fault those extensions haven’t been folded into the main language. What’s your point?

                            1. 1

                              The extensions haven’t been folded into the main language because they’re completely unnecessary. You do not need GADTs to write Haskell. Advanced type system trickery is exactly the sort of unnecessary crap that a ‘Boring Haskell’ movement should be not using.

                              1. 1

                                Many of those extensions are actually less well-thought-out than Haskellers think.

                                It is widely acknowledged that, as a general rule, orphan instances are bad. One problem with them is that they allow third-party modules to introduce distinctions between types that were originally meant to be treated as isomorphic. (At least assuming you are a well-meaning Haskeller. If you wanted to allow third parties to subvert the meaning of your code, you would use Lisp or Smalltalk, not Haskell.)

                                Then GADTs and type families are dangerous for exactly the same reason.