Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • All great industrial apps are DSLs for specific domains, because often time end users are much smarter & craftier than developers. Some great examples: - AutoCad (vector drawing DSL on top of Lisp) - Mathematica (symbolic algebra DSL - Lisp & C) - Aspen One (Thermodynamics/Chemistry DSL on FORTRAN) - COMSOL (Multiphysics DSL C++) - Verilog (FPGA design DSL C) and also general purpose tools like Regex, XLA, CERN/Root, SQL, HTML/CSS,...
  • TIL `a ?: b`, that's actually pretty nice, a bit like Haskell's `fromMaybe b a` (or `a <|> b` if b can also b "empty")

    and I do like `#define _(e...) ({e;})` – that's one where I feel the short macro name is OK. But I'd like it better if that were just how C worked from the get-go.

    Very nice discussion at the end of the article. There are good things to be learnt from this code and its discussions even if you disagree with some or even most of the style.

  • Yes, '?:' is also known as the Elvis operator [1][2]. I sometimes use it in other languages such as Groovy. But I don't use it in C because this happens to be a GCC extension [3][4] and I've often had to compile my C projects with compilers that do not support GCC extensions. The C standard [5] defines the conditional operator as:

      conditional-expression:
        logical-OR-expression
        logical-OR-expression ? expression : conditional-expression
    
    So per the C standard there must be an expression between '?' and ':' and an expression cannot be empty text. To confirm this we need to check the grammar for expression, which unfortunately is a little tedious to verify manually due to its deeply nested nature. Here it is:

      expression:
        assignment-expression
        expression , assignment-expression
    
      assignment-expression:
        conditional-expression
        unary-expression assignment-operator assignment-expression
    
      unary-expression:
        postfix-expression
        ++ unary-expression
        -- unary-expression
        unary-operator cast-expression
        sizeof unary-expression
        sizeof ( type-name )
        alignof ( type-name )
    
      assignment-operator: one of
        = *= /= %= += -= <<= >>= &= ^= |=
    
      ... and so on ...
    
    The recursion goes further many more levels deep but the gist is that no matter whichever branch the parser takes, it expects the expression to have at least one symbol per the grammar. Perhaps an easier way to confirm this is to just have the compiler warn us about it. For example:

      $ cat foo.c
      int main(void) {
          if () {}
      }
    
      $ clang -std=c17 -pedantic -Wall -Wextra foo.c && ./a.out
      foo.c:2:9: error: expected expression
          2 |     if () {}
            |         ^
      1 error generated.
    
    Or more explicitly:

      $ cat bar.c
      #include <stdio.h>
      int main(void) {
          printf("%d\n", 0 ?: 99);
          printf("%d\n", 1 ?: 99);
      }
    
      $ clang -std=c17 bar.c && ./a.out
      99
      1
    
      $ clang -std=c17 -pedantic bar.c && ./a.out
      bar.c:3:23: warning: use of GNU ?: conditional expression extension, omitting middle operand [-Wgnu-conditional-omitted-operand]
          3 |     printf("%d\n", 0 ?: 99);
            |                       ^
      bar.c:4:23: warning: use of GNU ?: conditional expression extension, omitting middle operand [-Wgnu-conditional-omitted-operand]
          4 |     printf("%d\n", 1 ?: 99);
            |                       ^
      2 warnings generated.
      99
      1
    
    [1] https://kotlinlang.org/docs/null-safety.html#elvis-operator

    [2] https://groovy-lang.org/operators.html#_elvis_operator

    [3] https://gcc.gnu.org/onlinedocs/gcc/Syntax-Extensions.html

    [4] https://gcc.gnu.org/onlinedocs/gcc/Conditionals.html

    [5] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3299.pdf

  • ```

    #define _(e...) ({e;})

    #define x(a,e...) _(s x=a;e)

    #define $(a,b) if(a)b;else

    #define i(n,e) {int $n=n;int i=0;for(;i<$n;++i){e;}}

    ```

    >These are all pretty straight forward, with one subtle caveat I only realized from the annotated code. They're all macros to make common operations more compact: wrapping an expression in a block, defining a variable x and using it, conditional statements, and running an expression n times.

    This is war crime territory

  • Some of these are wrong to. You can encounter issues with #define

    #define $(a,b) if(a)b;else

    due to not having brackets. So it's just extremely lazy to.

  • Much as a Real Programmer can write FORTRAN programs in any language, Whitney can write APL programs in any language.
  • This is a good use of macros. I understand people are frightened by how it looks but it’s just C in a terse, declarative style. It’s mostly straightforward, just dense and yes - will challenge you because of various obscure macro styles used.

    I believe “oo” is probably an infinity error condition or some such not 100% sure. I didn’t see the author discuss it since they said it’s not used. Was probably used during development as a debug printout.

  • > This is a good use of macros.

    no.

  • From the article

    >These are all pretty straight forward, [...] wrapping an expression in a block, defining a variable x and using it, conditional statements, and running an expression n times.

    Making your reader learn some ad-hoc shorthands you wrote to avoid declaring blocks, defining variables or writing conditions in my book is very impolite

    Style doesn't need to be innovative.

  • I agree, some of the macros are very useful, and I've found myself wanting DO(n, code) as a simpler for-loop construct. In my own code, when I have some dozens of small things (like opcodes or forth words or APL operators), I specifically do want a "one-liner" syntax for most of them. The individual elements are usually so small that it's distasteful to spend 10 lines of code on them, and especially because the real understanding lies in the 'space between', so I want to see a large subset of the elements at once, and not put code-blinders on to focus on one element at a time.
  • Kudos on not just taking a combative stance on the code!

    This was a very fun read that I'm fairly convinced I will have to come back to.

  • There are best or accepted practices in every field.

    And in every field they work well for the average case, but are rarely the best fit for that specific scenario. And in some rare scenarios, doing the opposite is the solution that fits best the individual/team/project.

    The interesting takeaway here is that crowd wisdom should be given weight and probably defaulted if we want to turn off our brains. But if you turn on your brain you will unavoidably see the many cracks that those solutions bring for your specific problem.

  • Having a solid product that solves a problem well can be orthogonal to how well a codebase lends itself to readability, learning curve, and efficiently ramping up new developers on a project.

    Just because you succeed at one says nothing about other practical and important metrics.

  • That's why I hate them being called "best" practices. No, they aren't the best practices, they are the mediocre practices. Sometimes, that's a good thing (you don't want to have the really bad results!), but if you aim for the very best practices, all of them will hold you back. It's basically a tradeoff, sacrificing efficiency / good performance in exchange for maintainability, consistency and reliability.
  • Link is dead :(
  • I was curious about Shakti after reading this and the comments, so followed the link to shakti.com on Wikipedia. It seems it now redirects to the k.nyc domain, which displays a single letter 'k'.

    I wondered if I was missing something, so looked at the source, to find the following:

      <div style='font-family:monospace'>k
    
    Nothing but that. Which is, surely, the HTML equivalent of the Whitney C style: relying on the compiler/interpreter to add anything implicit, and shaving off every element that isn't required, such as a closing tag (which, yes, only matters if you're going to want something else afterwards, I guess...). Bravo.
  • k
  • could have been `<pre>k`
  • You shouldn’t have seen that. By now the cleaners must have gotten to you and erased your memory of these events.
  • IMO this is a really good blog post, whatever you think of the coding style. Great effort by the author, really good for eight hours' work (as mentioned), and some illuminating conclusions: https://needleful.net/blog/2024/01/arthur_whitney.html#:~:te...
    by svat
  • The way to understand Arthur Whitney's C code is to first learn APL (or, more appropriately, one of his languages in the family). If you skip that part, it'll just look like a weirdo C convention, when really he's trying to write C as if it were APL. The most obvious of the typographic stylings--the lack of spaces, single-character names, and functions on a single line--are how he writes APL too. This is perhaps like being a Pascal programmer coming to C and indignantly starting with "#define begin {" and so forth, except that atw is not a mere mortal like us.
  • We know, the beginning of the article tells us his C code is APL-inspired. So many comments that just summarize the article on a surface level.
  • Would learning J work instead?

    It’s probably more accessible than APL since its symbols can be found on conventional keyboards.

  • My first thought was "oh, this just looks like a functional language" but my next thought was "with the added benefit of relying on the horrors of the C preprocessor."
  • >This is perhaps like being a Pascal programmer coming to C and indignantly starting with "#define begin {" and so forth

    Ah, like Stephen Bourne

  • It looks like a weirdo C convention to APLers too though. Whitney writes K that way, but single-line functions in particular aren't used a lot in production APL, and weren't even possible before dfns were introduced (the classic "tradfn" always starts with a header line). All the stuff like macros with implicit variable names, type punning, and ternary operators just doesn't exist in APL. And what APL's actually about, arithmetic and other primives that act on whole immutable arrays, is not part of the style at all!
  • > The way to understand Arthur Whitney's C code is to first learn APL

    This is the main insight in my breakdown of the J Incunabulum:

    https://blog.wilsonb.com/posts/2025-06-06-readable-code-is-u...

    When I first encountered it years ago, the thing was impenetrable, but after learning APL to a high level, it now reads like a simple, direct expression of intent. The code even clearly communicates design tradeoffs and the intended focus of experimentation. Or more on the nose, to me the code ends up feeling primarily like extremely readable communication of ideas between like-minded humans. This is a very rare thing in software development in my experience.

    IMHO, ideas around "readable code" and "good practices" in software development these days optimize for large, high-turnover teams working on large codebases. Statistically speaking, network effects mean that these are the codebasese and developer experiences we are most likely to hear about. However, as an industry, I think we are relatively blind to alternatives. We don't have sufficient shared language and cognitive tooling to understand how to optimize software dev for small, expert teams.

  • They're all macros to make common operations more compact

    I read the J Incunabulum before encountering this, and the point that stands out is that you don't start by jumping into the middle of it like many programmers who are familiar with C will do; the macros defined at the beginning will confuse you otherwise. They also build upon previous ones, so the code ends up climbing the "abstraction ladder" very quickly. I personally like the Iterate macro (i), for how it compresses a relatively verbose loop into a single character; and of course in an array language, the iteration is entirely implicit.

    In other words, I believe the reason this code is hard to read for many who are used to more "normal" C styles is because of its density; in just a few dozen lines, it creates many abstractions and uses them immediately, something which would otherwise be many many pages long in a more normal style. Thus if you try to "skim read" it, you are taking in a significantly higher amount of complexity than usual. It needs to be read one character at a time.

    As someone who has spent considerable time working with huge codebases composed of hundreds of tiny files that have barely any substance to them, and trying to find where things happen becomes an exercise in search, this extreme compactness feels very refreshing.

  • Adding some comments would have been advisable, but I guess he doesn’t need comments.

    APL taught me the importance of comments - if I didn’t comment my code thoroughly I would forget how it works and what it did as soon as I moved away from the keyboard. It is a cruel language.