1. 6
  1. 5

    There’s a convention in C – and even some other languages, I’ve heard – to call loop indices i. Would it be easier to read code that used variables named index instead?

    I’d hazard that the answer might be yes, for a beginner: if it’s your first time seeing a for loop, having to pick up the local slang at the same time you’re wrapping your head around the concept of iteration might be distracting noise.

    But you can have both: you can present non-idiomatic code to beginners, who are already having enough trouble parsing a brand new syntax and understanding brand new concepts, and then explain common conventions afterwards.

    So to repeat: Haskell code is too short. In particular, a ton of the example code in various resources designed to teach Haskell to beginners is too short.

    I agree with the second part of this, but the generalization sounds like “You should be writing for (int index = 0; ...).”

    (But is the term “index” any more meaningful, to someone who is encountering index-based iteration for the first time? Perhaps that term is just as arbitrary as i, to a beginner.)

    map :: (a -> b) -> [a] -> [b]
    

    Does this clarify that x above is a thing while xs is a list?

    This is interesting to me: you know xs is a list because it appears on the right-hand of the : constructor. You don’t need a type annotation for that; it can’t not be a list. But of course you can’t know that if you aren’t used to reading Haskell.

    map f (element:list) = (f element) : map list
    

    I don’t think this is really much better than x:xs. Sure, list is a list. But lots of things are lists. It doesn’t tell you anything about this list, except its type – which the type signature already tells you.

    map f (head:tail) = (f head) : map tail
    

    Do the terms “head” and “tail” make more sense? That’s probably another learned convention.

    map f (car:cdr) = (f car) : map cdr
    

    Crystal clear.

    map f (first:rest) = (f first) : map rest
    

    I think that’s how I would write it.

    map f (firstElement:restOfTheElements) = (f firstElement) : map restOfTheElements
    

    Is “element” a learned term, or an intuitive one for native English speakers? It’s hard to remember.

    On single letter module imports: this feels like a very different sort of argument than “element is more clear than x.” Single-letter module imports make code harder to read difficult precisely because it’s not a convention: if every file has its own set of custom module abbreviations, I have to learn what all of them are supposed to mean before I can read the file. (I think there’s an argument for consistent abbreviations used across a codebase, which have a one-time cost associated with learning them, but I don’t think that’s what the article is raging against.)

    I was interested to see that the blog is generated in Hakyll. I wonder if that was the case when the article was posted (nearly a year ago). Seems not.

    1. 8

      When I was being taught Haskell, everywhere I would see functions name “sht” or “rp” or some other combination of up to 4 letters. I didn’t like this at all, even way past the “beginner” stage, because it was impossible to decipher what the heck a function or variable was. So I started writing code with descriptive variable names: withNoDecreasing, recordFixpoints, and so on, and I write code like that to this day.

      But what I notice is that it becomes increasingly hard to see patterns in your code when the variable names get very long. Things that would look very familiar, like f <$> m1 <*> m2, would start getting really “stretched out”, to the point where maybe a part of the expression is on the next line, or put into a let expression to avoid making the line 200 characters long. Now it’s not so clear what’s going on; and now, too, you’re pushed to assign names to previously “intermediate” computations.

      My point is, I didn’t just learn conventions such as x and xs, a and b, or m and f, but I also learned the “shape” of common types of computations (like a function being applied to two arguments within an applicative functor). Renaming the variables doesn’t make the code more readable to me for the reasons you described, but also because I can’t recognize these “shapes”.

      1. 2

        Now it’s not so clear what’s going on; and now, too, you’re pushed to assign names to previously “intermediate” computations.

        And this is usually a good thing. Decompose and abstract until it fits your screen and brain.

      2. 5

        I like the way you’ve thought through this, kudos! Personally, I agree that first:rest is probably the most intuitive syntax, and has the benefit of being relatively short as well!

        For index-based iteration, I try to use names that have meaning within the problem domain. For example, rowIndex and columnIndex if I’m iterating a matrix generically, or personIndex for iterating a list of Person records (or whatever). Part of the value I see here, though, is that it is harder to mix the indexes up in a nested loop. It also facilitates moving code around as there’s less chance of accidentally shadowing i.

        1. 3

          Haskell conventionally abbreviates index to ix, which I rather like. If I have a list foos, then I might have an index into that list called fooIx, which I rather like. Reads much better than i and then j to me.

          1. 1

            I see an actual problem if you use both i and j in nested loops - those are so easily mixed up at a glance.

          2. 3

            The only module names I ever abbreviate to T are Data.Text, and occasionally some of its submodules (Data.Text.Encoding, Data.Text.IO, …).

            1. 2

              The introductory “totally subjective and unfair, but still correct, bucketing of the current PL nerd landscape” is super weird in that there’s an ML bucket but half of the other buckets are also ML. F# is basically OCaml.NET with cool extras like type providers; Haskell has far more things but it’s also from the ML family; even Rust/Swift have ML’s type definition structure, just not the global type inference. With that out of the way…

              it’s disappointing to see just a rant about variable names and teaching polymorphism too early and stuff.

              I was honestly 100% expecting some interesting bug story or something about long names actually having a big performance impact ;(

              cannot for the life of me figure out why anyone would ever use the non-qualified style of import for a piece of code that is longer than a screen long. Not even perl would do this to you

              Ruby on Rails anyone? All the things in that environment come “from magic”, including methods on regular objects that were actually patched in by ActiveSupport.

              With a strict type system it’s actually fine to do this.

              1. 2

                What would a blog post about programming languages be without a stab at Perl? sigh