• chaboud 5 hours ago

    I thought this was going to be a bit of dry humor before I followed the link. It’s actually serious, and fairly informative (albeit a bit scary).

    The first place I worked had a soft-but-hard rule (enforced by ridicule) that single-letter variable names not be used. Loop counters like i, j, and k were frowned upon. However, counters like ii, jj, and kk were fine for matrix operations, xx and yy for image loops, etc.

    There were a couple of key guidelines to follow:

    1. If it can have more meaning than sample, pixel, or matrix position, give it more meaning, but two characters repeated is fine for loop counters (again, with meaning).

    2. Whatever you use should be quickly findable with text search tools of the era (the late ‘90’s).

    Someone shouldn’t have to use source inspection assistive coding tools to parse the code you write. When someone else (or you, in a few months) comes upon this code and needs to understand it, they should be able to use find to do it.

    • gus_massa an hour ago

      Why is ii better than i???

      I may understand that instead of x,y it may be better to use px,py for positions and vx,vy for speed. Perhaps xx,xy for position. But I'd never use yy.

      • jmillikin an hour ago

        I use `ii' and `jj' etc for all my loop counters, because it's easier to search for in any editor.

        Searching for `i' requires learning the local editor's way of marking word boundaries, otherwise it'll highlight every instance of the letter `i' which is useless.

        Searching for `ii' has less noise because very few words have that sequence in them, and `jj' is even rarer.

        • throw4950sh06 an hour ago

          You need better tooling. My editor can highlight all references using the type checker.

        • pxx an hour ago

          I've seen this specific example used to speed up find-next, but it's not like the false positives are frequent enough to bother.

        • leni536 3 hours ago

          Optimizing for grep is fair, however searching for whole words is typically possible in most contexts. I don't have a problem for grepping single letter variable names, although I admit that whole word or word boundary search is not as obvious and slightly less convenient than just plain substring search.

        • phforms 3 hours ago

          Clojure developers also often make use of single-letter variable names, following conventions in `clojure.core`: https://guide.clojure.style/#idiomatic-names

          My rule of thumb is to only use short names in the local scope of a function and have them follow conventions within the language community. Constrained like this, I see no harm in using them, since their meaning is clear within the context of their usage and there is usually no need to search for them. Of course, if the variable has a more specific meaning, I choose a more specific name.

          • actuallyalys 2 hours ago

            I had the thought of making one for Clojure. I didn’t realize one already existed, so thanks!

          • semi-extrinsic 4 hours ago

            Fortran's implicit typing sends its regards from an IBM 704.

            • microtherion 2 hours ago

              Yes, I was thinking of this. Picturing a bastard child of Haskell and Fortran right now, where the type of a variable is determined again by the first letter of its name.

            • Dwedit 5 hours ago

              C# basically forces "e" to be the name of EventArgs in any event handler. You can change it, but it will always default to "e".

              • ZeroClickOk 4 hours ago

                I have a hard time normalizing existing codebases where coders insist on using `catch (Exception e)` instead of the default `catch (Exception ex)` and then there are clashes in event handlers with try/catch due to the same variable name...

              • dvt 4 hours ago

                I know this is mostly for Haskell, but I would add (used in various languages):

                    e: event
                    k: key in hash map, dictionary, etc.
                    o: object or reflected object types
                    v: value in hash map, dictionary, etc.
                    t, u, v, w: vectors (borrowed from math)
                
                Would be fun to think about this for a while and come up with a more serious dictionary with examples and links to code.
                • larodi 4 hours ago

                  How come are this specific for Haskell and not universal?

                  My errors are caught as ‘e’, files are ‘f’, apparently indices are i, j, k, for as long as people been writing C.

                  K/V are keys and values, and, well, ‘t’ is time. I’m sure you can guess all other…

                  I really don’t understand how is this a specific language’s thing.

                  • dvt 4 hours ago

                    > How come are this specific for Haskell and not universal?

                    The author says as much. There's overlap for sure, but all the monad stuff, for one, is mostly Haskell-(or functional language rather)-specific.

                    • _jackdk_ an hour ago

                      (Author here.)

                      I think the amount of polymorphic code Haskell enables means that single-letter variables are more common than in other languages (where they're usually confined to narrow scopes), and there's a lot less to say about them. Another HN commenter on this post mentioned IEnumerable<T>, which is very similar: you know nothing about T, but you need to call it _something_, and there's a common understanding in the community that you call such type variables <T>.

                      Haskell's typeclasses then mean you've got a lot of different concepts that could show up in a type signature, but they're still polymorphic and there's not much you _can_ say beyond "this is Foldable", or "this is a Monad". So an even broader convention has emerged.

                      • PaulHoule 4 hours ago

                        People used I,J,K,L for integers in FORTRAN and BASIC for that matter, in fact early FORTRAN determined the type based on the first letter of the name.

                        • kergonath 3 hours ago

                          GOD is REAL except if declared otherwise :)

                      • jonathrg 4 hours ago

                        a is for array

                        b is for byte or buffer

                        c is for character

                        d is for destination

                        e is for event or error

                        f is for file

                        g is for graph

                        h is for handle

                        i is for index

                        j is also for index

                        k is for key

                        l is for length

                        m is for mode

                        n is for number-of

                        o is for object

                        p is for part

                        q is for queue or query

                        r is for reader

                        s is for source

                        t is for time

                        u is for user

                        v is for value

                        w is for word

                        x is for x component

                        y is for y component

                        z is for z component

                        • RheingoldRiver 3 hours ago

                          A is for Array:

                          A is for array, some things in a list

                          B is for byte, how computers exist

                          C is for character, one letter in a word

                          D is for destination, where results are stored

                          E is for event, interaction in the DOM

                          F is for file, sometimes written to ROM

                          G is for graph, some edges and nodes

                          H is for handle, it references loads

                          I is for index, add 1 as time flies

                          J is for index, but different from i's

                          K is for key, in associative arrays

                          L is for length, as in how many j?

                          M is for mode, and not like ice cream

                          N is for count, like sheep in a dream

                          O is for object, some data and code

                          P is for part, to share out the load

                          Q is for queue, process one at a time

                          R is for reader, I can't think of a rhyme

                          S is for source, and you're doing great

                          T is for time, 429 makes you wait

                          U is for user, like you'll be one day

                          V is for value, associated to k

                          W is for word, collections of c

                          X is for x-component when doing 3D

                          Y is for y-component like x is again

                          Z is for z-component, we got to the end!

                          [note, i am not sure what is meant by P = part, and I know some of these are kind of lame and got worse as I got impatient to finish]

                          [i wrote this myself, it is not ai-written]

                          • rphln 3 hours ago

                            Would've been perfect had you said jndex.

                            • _jackdk_ an hour ago

                              Good catch, I'd forgotten "reader" (as in `ReaderT`). Thanks.

                          • w10-1 3 hours ago

                            I agree with the semantics, but restrict usage to the very narrow case where the variable has no other use than to convey a value from a visible outer scope to a visible inner scope, where both together are less than a few lines. In that case I prefer the single character over a longer name precisely because it is clearer.

                            Note that x,y,z don’t really qualify as abbreviations since they are the full name of the component, so they can be used when the outer scope is not visible — eg as a parameter.

                            • vips7L 5 hours ago

                              Part of the reason why Haskell never took off. This stuff is incomprehensible.

                              • dmkolobov 2 hours ago

                                I know this is a troll comment, but the situations you see these variables are in highly-polymorphic( generic ) code in which there are no more descriptive names.

                                It’s like the use of `T` as a name in most Java or C++ generics.

                                • yCombLinks 22 minutes ago

                                  I don't see it as a troll comment

                              • yawnxyz 4 hours ago

                                js land:

                                   $: jQuery
                                   _: lodash, underscore libraries
                                   e: usually events like keyboard, button clicks, form submits, when preventing form actions...
                                
                                any other js quirks?
                                • t-3 2 days ago

                                  I like to use 'n' as an index when decrementing the counter.

                                  • o11c 4 hours ago

                                    Edit: I'm trying to merge in all the ones other people have added; most are original still though.

                                    I don't Haskell, thankfully, but a lot of these are applicable broadly. Some notes/additions I've seen in other languages (but far more often these are obvious).

                                    (only vaguely sorted, since many aren't contiguous):

                                      a can mean "argument" particularly; also "arg"
                                      a,b,c,d can be anything, but slightly more common for floats and/or coefficients, or matrices
                                      a is often "array"
                                      a can be ascii (either as string in general, or in specific)
                                      a can be "allocator" or "allocation"
                                      b for bytes is not shameful, but sometimes it means "buffer", "blob", or "bits" instead. Can be used for just an element or for a sequence of them.
                                      b can be "builder" (a utility class used to construct values instead of using "new" directly)
                                      b can be "base"
                                      b,e for begin/end (often for iterators/indices)
                                      c is often used for "character", "code unit", "codepoint"; ch/cu/cp can disambiguate
                                      c can also be "constant" (either a value or a modified type)
                                      c can also be "container"
                                      c can also be "condition" (a boolean)
                                      c can also be "context", but more often ctx or ctxt
                                      d is rarely used for "data", but more often it's expanded or v is used instead
                                      d can be "direction", but more often dir
                                      d,s can be destination/source
                                      e can also be "event"; ev if needed
                                      e can also be "element" (member of an array, member of an XML-like tree)
                                      f is common for floats (even if called "double" or "real") or fixed-point numbers
                                      f is often file; fp/fo may be common depending on the language
                                      g for graph
                                      h for handle
                                      i can also be "instruction"
                                      i,o,r,w can be input/output
                                      i,j,k are common as indices *or* iterators, corresponding to x, y, z if meaningful.
                                      l is rare but can be a 4th index or iterator if needed, usually does not related to w though.
                                      l is probably more common as "length"/"limit"
                                      l can be "long" in various senses
                                      l,h can be low/high, but more often lo/hi
                                      l,r can be left,right (also lhs,rhs), especially of a binary operator (or occasionally a unary one, e.g. to disambiguate prefix/suffix)
                                      m = mode?
                                      n,+m are often specifically the upper bound of a range, similar to l
                                      n can be node (of a tree/graph)
                                      o is an arbitrary object (usually, when explicitly not a primitive or string)
                                      o can be "origin"
                                      p,+q are not rare as "predicate"
                                      p,q can also be prime numbers
                                      p is for pointer
                                      p = part?
                                      p,q = percent chance (often normalized to 0-1 though)
                                      q for queue
                                      q for query/request
                                      q for "quad"
                                      q,r,n,d for fractions
                                      r (and rv/res) is sometimes used ambiguously - should it be the return value of a function I called, or the value I'm planning to return? Can also stand for "reply".
                                      r can be "range"
                                      r can be "rectangle"
                                      r can be "register"
                                      r,c for row, column; origin is top-left and axes are flipped
                                      r,g,b,a are color components; other letters exist for other colorspaces
                                      s is often an arbitrary string
                                      s can also be symbol (sy/sym depending on context), statement (stmt), or set (often given a different kind of name)
                                      s can be "stack"
                                      t for table
                                      t for time
                                      t,u,v (usually uppercase) are arbitrary types in generics
                                      u,g,o for user/group/other
                                      v is often just an arbitrary data value (not meaningfully numeric/string/object), even without a container
                                      v,w,t,u can also mean "vector" specifically
                                      w = word
                                      u,v,s,t,q are used for 2D texturing
                                      u can be unicode
                                      w can be wide character, but good riddance
                                      w is for window
                                      x can also be "expression" in particular
                                      x,o,d,b can be hex, octal, decimal, or binary bases
                                      x,y,w,h are start/size of a rectangle
                                      x,y,z,w are used for 3D objects; w is often normalized to 1
                                      _ as a function or string affix means "call gettext on this now"; N_ means "remember to call it later"
                                      _,$ are sometimes injected as a general-utility global variable in languages with deficient standard libraries
                                    
                                    Mostly not mentioned, several of these are used uppercase for generic type arguments.

                                    I'm also ignoring all the various units and physical constants.

                                    I'm pretty sure about a dozen more passed through my head while writing this but I forgot them before I finished writing the previous entry, and I can't be bothered to actually start scanning code.

                                    • artemonster 5 hours ago

                                      As someone who is used to code in python and typescript explain to me in layman terms why the HELL would you need something like this: "...in lenses or other optics. The simplest complete example is a Lens s t a b: it can extract an a from an s and overwrite it with a b. Doing so would produce a value of type t."

                                      • pona-a 3 hours ago

                                        Lenses seem to be a functional pattern that groups getters and setters into composable functions.

                                        Consider pair(a, b) with first(pair) and rest(pair). I can define first-lens and rest-lens for [get-first, set-first] and [get-rest, set-rest] respectively. I could use them as simple getter/setter groupings with lens-view(rest-lens, pair) or lens-set(first-lens, 4, pair), which isn't so useful by itself, or I can compose then to get third-lens with lens-compose(rest-lens, first-lens), allowing me to lens-set(third-lens, 7, pair) without a nested call.

                                        I just tried to summarize what I could read [1] [2] [3] and I don't claim to entirely understand it, but it's still better than the sibling comment dismissing it as abstract nonsense.

                                        [1] https://docs.racket-lang.org/lens/lens-intro.html

                                        [2] https://hackage.haskell.org/package/lenses-0.1.4/docs/Data-L...

                                        [3] https://theswiftdev.com/lenses-and-prisms-in-swift/

                                        • dmkolobov 2 hours ago

                                          It’s essentially a way to give types to “mutable” views into larger data structures.

                                          s - the larger data structure

                                          a - a view value

                                          b - a “update” value

                                          t - the data structure after an update is applied

                                          Of course, everything is still immutable in the same way that cons-ing a value onto a list is immutable.

                                          The change in type between s and t allows you to nest these views, hide information, etc.

                                          • _jackdk_ an hour ago

                                            You've got the right general idea but the difference between `s` and `t` is not for nesting - you can write a composition operator for a "simple" lens where s=t and a=b.

                                            `b` and `t` are just for type-changing updates, like when you replace a field in a tuple or every element in a list.

                                          • rcxdude 2 hours ago

                                            Basically because in languages where everything is immutable, 'setting' a value nested in a datastructure is a big hassle. Lenses give you a means to effectively do foo.bar.x = "baz" succinctly (and a few other bits besides).

                                            • kristopolous 4 hours ago

                                              Haskell is by mathematicians and for mathematicians. I know this sounds absurd but many actually find it to be the easiest programming language for them. I've met mathematicians who claim they were not able to code until they found Haskell.

                                              • cstrahan 4 hours ago

                                                Sure, I can do that.

                                                Why the HELL would you need objects and object oriented programming? Wherever you do foo.blah(), you could just do foo_class__do_blah(obj)… so what do objects really grant you that you can’t do with basic procedural programming?

                                                Why do you need Python’s function decorators? You can just manually copy and paste whatever logic you need into each and every function, instead of decorating them.

                                                Why have an iterator protocol? Every datum can have it’s own bespoke way of iteration that you simply have to memorize on a case by case basis (and can not be abstracted over in any common way), why make everything conform to the same interface?

                                                Why have anonymous functions that can close over their lexical scope (what people usually refer to as “closures”)? Consider:

                                                  let x = 0;
                                                  let inc = () => x++;
                                                
                                                You could just write:

                                                  function inc(bindings) { return bindings.x++; }
                                                
                                                And then remember to pass along bindings whenever you need to call inc. Besides, that’s more or less what’s going on behind the scenes.

                                                I could keep going.

                                                We have these facilities not because they are essential (go program in assembler, and you’ll see how few programming techniques and concepts your processor actually cares about), but because they are nice to have. They are nice to have because they provide us tools for abstraction, so by solving the general case, you solve a host of concrete cases.

                                                Lenses let you abstract over data traversal and projection.

                                                Maybe you don’t appreciate that, and that’s fine. There are C programmers who don’t mind writing 100 different hash tables for different keys and elements. If you are a Python/TS developer, I’m pretty sure you’d find that surprising — if you want a dictionary/hashtable, you just use a dict and call it done — you wouldn’t go build a new dictionary class for each and every key-type/value-type pair.

                                                Different people have different tolerances for the drudgery of solving the same problem for the 100th time.

                                                However: while I don’t understand why someone would choose vanilla ice cream if they’re given the option of chocolate, I wouldn’t act flabbergasted in such a scenario. Why would I? Why should I expect my desires to be the same as everyone else’s?

                                                The only reason I can think of that someone would express shock at someone choosing vanilla over chocolate is if they think that the preference for vanilla indicates something is fundamentally wrong them. Or in other words: being a condescending bigot.

                                                That’s kinda how you’re coming off.

                                                In the event that you were actually asking in good faith: if you like to broaden your skills so you can finish programming work faster, I’d recommend reading up on lenses. If you don’t care about that, that’s fine too, but I’d recommend against expressing shock at the fact that others do care about these types of things.

                                                • _jackdk_ an hour ago

                                                  I dunno mate, I think GP's a little sharp, but it's great that he asked his question and expressed just how baffled he was instead of closing the tab and going "those Haskellers are a bunch of loonies". But I think you came out swinging here in a way that's not helpful. Accusing GP of bigotry because he didn't understand, and asked a question to understand?!

                                                  I agree with the technical points in your post and disagree with its venom. It's important to notice patterns and abstract over them, and to teach people to spot commonalities they haven't yet noticed in their code. Next time I think you'll have better luck starting with the assumption of good faith and going from there.

                                                  • rcxdude 2 hours ago

                                                    To be fair, idiomatic python code almost never has the constraints that would make a lens useful. Python programmers would just mutate their datastructures, or if they are trying not to mutate the original, copy it and then mutate it, or use a closure to represent the access to the nested structure, something that is about as succinct as representing it via lenses in functional languages. That's why it seems kinda unnecessary as a construct to someone who's only used to thinking about coding that way.

                                                  • _jackdk_ an hour ago

                                                    (Author here.)

                                                    It's a very fair question. I'm pretty sure lenses were invented because Haskell's built-in records are not ergonomic, but then the community discovered that they're extremely useful, general, and flexible.

                                                    Here is a Redux (JS) example of the problem that lenses help solve, where you are working with immutable data and want to replace one field of a deeply-nested structure: https://redux.js.org/usage/structuring-reducers/immutable-up...

                                                    You can think of a lens as a getter/setter, stored together as a first-class value. I'm not great at TS but hopefully you can see what I'm getting at with these types:

                                                    A Lens<S,T,A,B> is a pair of functions:

                                                    * get: (s: S) => A

                                                    * set: (s: S, b: B) => T

                                                    The most obvious lenses you can make are lenses that represent record fields (object properties?). But you can make lenses into other things, like a function that returns a lens into the Nth bit of an integer, or a function that takes a map key and returns a Lens from Map<K,V> to Optional<V>.

                                                    You use lenses by passing them to functions that then operate on your data. Three common ones are `view`, `set`, and `modify`:

                                                    * view: (lens: Lens<S,T,A,B>, s: S) => A

                                                    * set: (lens: Lens<S,T,A,B>, s: S, b: B) => T

                                                    * modify: (lens: Lens<S,T,A,B>, s: S, f: (a: A) => B) => T

                                                    So far, it doesn't look like we've bought much. But the real payoff comes from when you notice that lenses compose:

                                                    * compose: (outer: Lens<S,T,X,Y>, inner: Lens<X,Y,A,B>) => Lens<S,T,A,B>

                                                    This solves the "nested record problem" because you can compose the lenses for each field in the chain to get a lens from the outer structure to the field you're interested in, and functions like `set` and `modify` will correctly copy all the fields at each layer when you call `set` or `modify`. Haskell's lens libraries give you compact operators for doing this, so it all looks neat and tidy.

                                                    A reasonable response to all this machinery is, "OK, your language has bad records and you built a library to paper over them. So what?" To this I say:

                                                    1. Every language has this problem when working with nested immutable data, though not every language has the tools to compactly encode lenses and make them ergonomic to use; and

                                                    2. Lenses generalise to give you other "optics". A `Traversal<S,T,A,B>` picks out zero or more `A`s from `S`, and can replace them all with `B`s. `modify` and `set` then generalise, and perform their update on _every_ matching `A` selected by the `Traversal`, and you also get `toListOf`, which collects all matching `A`s into a list. There are other optics too, but this post is getting long.

                                                    Once you have the more powerful optics, you get a very expressive toolkit for querying and updating data, that works on any data structure that has optics defined. (And Haskell has tools to derive many optics automatically.)

                                                    Chris Penner's wonderful book, _Optics By Example_[1], has a bunch of exercises that involve slicing and dicing a big slab of Kubernetes YAML with queries like "collect a list of all 'containerPort's alongside their Pod's 'name' (from 'metadata')" and operations like "set a resource request of `memory: "256M"` for every container". The answers are extremely compact and elegant, and I don't know how you'd achieve anything similar with other methods.

                                                    I hope this helped, because there is definitely a really useful and powerful toolkit hiding behind that absurd-looking type signature.

                                                    [1]: https://leanpub.com/optics-by-example/