1. 33
  1.  

  2. 7

    This is one of the better written and clearly more informed reviews of learning Elm that I’ve read. This could be because this is just well written and balanced, without succumbing to the Middle Ground Fallacy as many others do and end up saying “oh it’s nice and all, but at the end of the day it’s just a language and all languages are basically the same…”. It could also be because so many of the other articles are full of hyperbole, and the authors feel their sensibilities have been offended because Elm doesn’t conform to their pet code formatting style. It could be both.

    Anyway…

    That’s the extent of the formal documentation, and then there is an example (which is very helpful, but I don’t think examples are a substitute for thorough documentation)

    This is a little confusing. The documentation linked to from this part of the article actually includes a written English description of what this function is for, and the type signature, and a code example demonstrating a specific use case.

    Here’s the suggested alternative example:

    filterMap : (a -> Maybe b) -> List a -> List b
    filterMap convertFn input = ...
    

    The suggested alternative example is arguably not clearer. I say “arguably” because there’s no right answer here. Why would one argument be called input? Aren’t both arguments inputs? What’s clear to one reader is a quandary to another.

    I think the people creating Elm have in general done a pretty good job of balancing the pragmatic and specific examples with not obscuring what is perhaps the more important concept of generality. This is aside from the contentious issue of generality in Elm regarding lack of typeclasses.

    String.length says merely “get the length of a string”. Is that the number of bytes in its UTF-8 encoding? Number of unicode codepoints? Number of UTF-16 words?

    That’s a good question, but again I’d provide the same style of answer. At what level of specificity do we want to communicate our ideas? What assumptions can we make about who will be reading this specific part of the documentation, and at what level their technical proficiency is. If this were more specific, would another person’s complaint then become “Oh I don’t care about all these irrelevant details! I just want to know how I get the length of a string!”

    Information hierarchy/architecture is not a trivial thing to solve, and again I think the Elm people have done a pretty good job of taking a position on where on the spectrum they want to be with this.

    I think the criticism around Date types is fair, and I remember in some of the larger Elm apps I’ve worked on, we’ve resorted to using moment.js through ports.

    Again, while I’m pushing back on some of the criticisms in the article, I think the article on the whole is well-written and valid.

    1. 4

      I find the Date situation to be great nowadays, you just have to bite the bullet and use justinmimbs/elm-date-extra.

      In fact that is a common pattern I’m starting to observe: you really have to trust the quality of a lot of libraries and just use them, without being afraid about (a) stability or (b) lock-in. We sometimes have the wrong instincts due to experience in languages that are less robust (addressing (a)), or less easy to refactor (cf (b)).

      See the author’s comment about NoRedInk/elm-json-decode-pipeline: I would never start a project without it if it involved json decoding!

      The awful SEO of the Elm ecosystem is really to blame for a lot of this confusion. It’s hard to find the community consensus about which libraries are good, and constantly stumbling onto deprecated versions is annoying.

      All in all Elm is awesome though.

      1. 2

        Oh, no doubt. In fact two of my three businesses lean pretty heavily on Elm. I’m certainly a fan, though I try as hard as I can to avoid “fanboyism”, and I think it’s important to recognise drawbacks — whether perceived or genuine — even in languages you enjoy.

        1. 1

          If you were starting today would you still pick Elm for those businesses? I don’t think there’s been much in the way of alternatives created in the past few years. I’m pretty sure Purescript was already around. Maybe ReasonML is newer?

          1. 2

            Yes, I would. Generally we try to keep all logic in Haskell on the server, and avoid logic in the UI. For my main business (and indeed for most businesses), our users are totally fine with full page reloads. When we do need a more complex and stateful UI, Elm is our first choice. It doesn’t have as many features as say PureScript, but I think that’s to our benefit. It certainly makes it easier to onboard UI developers who have only ever worked with JavaScript and React.

            I haven’t tried ReasonML, but as I understand it, it doesn’t enforce purity as strictly as Elm does. The following passage from their documentation worries me:

            An eye for simplicity & pragmatism. We allow opt-in side-effect, mutation and object for familiarity & interop, while keeping the rest of the language pure, immutable and functional.

            Some developers when learning Haskell get annoyed that they can’t just do IO anywhere, and then reach for unsafePerformIO. This is almost never a good idea. It’s better the developer just learns the new thing, and it’s better for the product and the business that we can guarantee there aren’t unsafe IO actions hiding throughout the codebase.

            YMMV, this is all just my opinion, man, etc. etc.

      2. 4

        (Note in advance: my apologies for writing that has turned out to be a overly-long ‘rebuttal with some sources’ for one element of your comment. The sources are just there because they are far more fun & informative than my comment (and because I want to be more like Hillel ^_^ ), not because I want to Argue On The Internet And Win. I also like the rest of your commentary! And I recognise my own comment might be too long to reply to, that’s fine.)

        I fully agree with Ben Hoyt and you that that the Elm docs for filterMap have a helpful description and example; but I’m not sure I agree with a subsequent comment you made. That comment seemed to assert that a purely abstract type description …

        filterMap : (a -> Maybe b) -> List a -> List b
        

        … might be better than an abstract one plus a concrete one:

        filterMap : (a -> Maybe b) -> List a -> List b
        filterMap convertFn input = ...
        

        You wrote:

        The suggested alternative example is arguably not clearer. I say “arguably” because there’s no right answer here. Why would one argument be called input? Aren’t both arguments inputs? What’s clear to one reader is a quandary to another. […] I think the people creating Elm have in general done a pretty good job of balancing the pragmatic and specific examples with not obscuring what is perhaps the more important concept of generality.

        My specific objection: considering a function’s data argument its main argument/input is ubiquitous. It is also ubiquitous in functional languages – just look at the fact that they (Elm, Haskell, etc.) reserve the last argument position for the data argument, so they can easily write pipelines where the data passes through curried functions.

        My more general objection: One does not teach by only offering the final level of understanding, but by connecting it to a learner’s present level of understanding. In this case, you want to teach the generality of the filterMap function. The abstract defintion is still there. The current docs already have the very abstract type defintion and the very concrete usage example. Adding the intermediate ‘definition with arguments named for their most common semantics’ makes it easier to move up and down the ladder of abstraction.

        Educational research has found ([Raws2014], cited in Teaching Tech Together [Wilson2019]) that “presenting examples helps students understand definitions, so long as examples and definitions are interleaved.” I realize that is not quite the same as “presenting examples will also help students understand abstractions”, but I do believe that that is also often true.

        • [Raws2014]: Rawson, Katherine A., Ruthann C. Thomas, and Larry L. Jacoby. The Power of Examples: Illustrative Examples Enhance Conceptual Learning of Declarative Concepts. Educational Psychology Review 27, no. 3 (June 2014): 483–504. doi:10.1007/s10648-014-9273-3. Reports that presenting examples helps students understand definitions, so long as examples and definitions are interleaved.

        • [Wilson2019] Greg Wilson (2019), Teaching Tech Together, section Concrete Examples.

        1. 2

          That comment seemed to assert that a purely abstract type description might be better than an abstract one plus a concrete one

          I’m sorry, that isn’t my position. I may have been clumsy in communicating that. To clarify: I believe the best documentation is a balance of both the concrete and the abstract.

          One does not teach by only offering the final level of understanding, but by connecting it to a learner’s present level of understanding.

          I completely agree. That’s why I said I think the Elm team have done a good job of balancing approaches, and that information hierarchy is non-trivial.

          1. 2

            I’m sorry, that isn’t my position. I may have been clumsy in communicating that.

            And/or I may have been clumsy in my interpretation of your comment :-) My apologies for the inconvenience.

            To clarify: I believe the best documentation is a balance of both the concrete and the abstract.

            Agreed, obv. I’m not so sure anymore which part of Hoyt’s proposal you objected to, but never mind. Have a nice day!

        2. 3

          When I learned Elm, I had the advantage of experience with Rust’s Iterator trait, so I already expected certain things about the function signatures of filter and map. When I saw Elm’s filterMap, just by looking at the types I knew there was only one way it could possibly work, and of course I was right.

          But I can imagine if I’d come to Elm straight from Python or JavaScript, I might find that function signature quite daunting. Other languages have quite a divide between the parameter and result types, while Elm’s optimised-for-currying syntax keeps things ambiguous. Other languages like to put the subject of a function as the first parameter, or even make it implicit, while Elm puts the subject right at the end, for the benefit of the |> operator. Other languages make every type implicitly nullable, or don’t declare types at all, while in Elm they’re blaringly eye-catching. Other languages that aren’t as strongly-typed make great efforts to give function parameters descriptive names, while Elm doesn’t bother to name parameters at all (at least, not in the docs).

          None of those things are problems, some of them are even benefits, but I’m not surprised that somebody new to Elm occasionally gets a bout of culture-shock. Elm has put a lot of resources into being kind and helpful to new users, but there’s that intermediate stage between “what’s this Maybe everywhere” and “why can’t I define my own typeclasses” where it could afford to be a little bit more like better-known languages. For example, by naming function parameters in the documentation — entirely superfluous for experienced Elm developers, but something familiar for intermediate developers to cling to in an unfamiliar environment.

          1. 2

            I recognise the culture shock. It is real, but at some point developers just need to take the plunge and learn something completely new, rather than just syntax swaps on ideas they’re already familiar with.

            With regards to your point about argument order, I remember this short talk does a pretty good job of explaining why even some of the most popular JavaScript libraries get it just dead wrong: https://www.youtube.com/watch?v=m3svKOdZijA

        3. 5

          I somewhat agree with the comment on docs. It took a while until it clicked for me that package.elm-lang.org IS the API reference. After that, I didn’t really use (external) search anymore, and had no problems with old docs. Seems like an important little detail that should’ve been explained somewhere.

          On comparing code size, it would’ve been a more interesting metric if the original was TypeScript, I think.

          Nice article. Elm is cool. 🙂

          1. 1

            a somewhat unrelated question: what is a ‘medium-sized’ web frontend?
            (not even sure what unit(s) of measure are )

            1. 1

              I find it interesting how many warts of Elm simply aren’t visible to people who haven’t experienced other MLs.

              The type system is very weak and thus very restrictive. Monads aren’t a thing, because typeclasses aren’t a thing. There’s no way to write API-generic code; the closest the language has to a holistic solution is to round-trip everything through an abstracting JSON codec. Elm is seven years old, it has had people asking for typeclasses for seven years, and the state of Elm is that, for the past three years, discussion has been locked and zero ideas have been put forward. Without modules and functors, it’s not possible to compose our way out of this quagmire either.

              Here’s a classic combinator. In Haskell it’s called Data.Maybe.maybe. In OCaml it’s called Option.fold.

              maybe : (a -> b) -> b -> Maybe a -> b
              maybe f b m = case m of
                  Just a  -> f a
                  Nothing -> b
              

              Elm doesn’t have it. I know this because not only did I yank this example from some two-year-old Elm code of mine, but in those two years, the closest Elm has come is Maybe.withDefault, which comes with an admonition to not use it. Why would an ML tell people to not use combinators? Like the language’s spelling suggests, this is very backwards from ML’s tradition!

              Handling of binary data evolved slowly. First, it wasn’t possible. Then, there were some kinds of byte-level decoders. Nowadays, there are some mid-level binary data parsers. But it’s not clear how I would simply write a PNG decoder. In the past two years since I’ve experimented with Elm, they’ve evolved somewhat, but I now have expectations, because I wrote a PNG decoder in Python in 54 lines, and the same code in Monte in 64 lines. This core feature in Python and Monte was a slapdash addition to Elm, cranked out without any attention to the use cases raised by the community. This inability to manipulate binary files easily was a complete showstopper for my purposes, and by the time Elm grew a prototype file and bytes API, I was done waiting.

              Like its namesake, Elm grows slowly. Fine. But don’t pretend that a slow-growing young language is more production-ready than a readily-extensible old language.