Hacker News new | ask | show | jobs
by jprzybyl 3528 days ago
I'm reminded of a quote from Moore in "Thinking Forth":

"A lot of conditionals arise from fuzzy thinking about the problem. In servo-control theory, a lot of people think that the algorithm for the servo ought to be different when the distance is great than when it is close. Far away, you’re in slew mode; closer to the target you’re in decelerate mode; very close you’re in hunt mode. You have to test how far you are to know which algorithm to apply."

"I’ve worked out a non-linear servo-control algorithm that will handle full range. This approach eliminates the glitches at the transitioning points between one mode and the other. It eliminates the logic necessary to decide which algorithm to use. It eliminates your having to empirically determine the transition points. And of course, you have a much simpler program with one algorithm instead of three."

"Instead of trying to get rid of conditionals, you’re best to question the underlying theory that led to the conditionals."

That's part of a chapter of the book called Minimizing Control Structures. Forth guys are crazy about taste, and if I've learned anything from reading their stuff, it's that chasing tasteful programming to its end gets very hard.

OP is right on the money. The hard thing is that it is a creative process, and takes a real understanding of the problem you're solving to do it. Worst of all, aside from the feeling of solving a puzzle well, the benefits only begin appearing much later. I'm glad the kernel team takes it seriously.

3 comments

Be it kernel programming or Forth, the cultural shock can hit hard.

Most programming languages try their best to make things easier than easy, in order to maximize programmers' productivity. The focus on this parameter alone over-promotes the "worse is better" or "good enough" mindset.

That doesn't fare well with kernel programming. It's even worse with Forth. As soon as your "word" (function in Forth lingo) has to deal with more than two or three variables, you're in trouble. As soon as you have more than two levels of control-flow structures, nightmares begin.

If you're looking for programming practice then I recommend Forth. It may or may not be a "practical" language for you, but it makes you really think about complexity.

Forth was practical when it was invented though.
Still is practical, OpenBoot/OpenFirmware is still what boots big IBM POWER servers. Not sure if Oracle still uses it but Sun did.

OpenBoot is basically like UEFI but less complex and written in a Forth dialect. It's quite cool.

> That's part of a chapter of the book called Minimizing Control Structures.

Since the book is creative commons and freely available, I decided to download the book and have a look. Figure 8.1, the "automatic teller" example is just downright hilarious. The author presents the code and throws a challenge at the reader: "Easy to read? Tell me under what condition the user’s card gets eaten." Here's the code:

  IF card is valid DO
    IF card owner is valid DO
      IF request withdrawal DO
        IF authorization code is valid DO
          query for amount
          IF request <= current balance DO
            IF withdrawal <= available cash DO
              vend currency
              debit account
            ELSE
              message
              terminate session
          ELSE
            message
            terminate session
        ELSE
          message
          terminate session
      ELSE
        IF authorization code is valid DO
          query for amount
          accept envelope through hatch
          credit account
        ELSE
          message
          terminate session
    ELSE
      eat card
  ELSE
    message
  END
(hopefully no transcription errors from copying out of the PDF…)

Yes, it's easy. It gets eaten if the owner is not valid. Took one glance.

The thing that makes me laugh is that the example is contrived. This is the sort of thing code editors solve. Even though I could quickly glance at it and decipher the conditionals by eye, if I were working with this I'd still throw it into an editor that shows me code scope. That makes it trivial and removes any chance of error. This is a problem that has been solved.

I'm sure I will get a lot of responses saying I've missed the point, and yes I'm aware that I've dodged it, but the author acts like the problem of navigating nested conditionals is somehow impossible, which I think is ridiculous. Not only do lots people do it every day, not only are there tools that help us do so, but there's pretty much no way to exist in this world without depending on software that is written this way (ever looked at the source for GCC, to pick one example?) Not saying it's right, but it's clearly not a deadly problem.

Just as taste is a subjective thing as is often hard to articulate... I think my biggest complaint about that code is that I had to read all of it to make sure there weren't other cases where the card gets eaten. And try to follow what was going on.

Taste-wise, an easy fix for this is to do early exits. Instead of nesting, you could easily do (I'm on mobile so no fancy formatting):

If card is not valid: return message

If user is not valid: eat card; return

Etc

Now that's my personal taste. It makes it way simpler to quickly skim and understand what's going on.

> Yes, it's easy. It gets eaten if the owner is not valid. Took one glance.

Nit-pick, but it seems that the card only gets eaten if the card is valid AND the owner is not valid. If the card isn't valid, the owner is never checked and the card isn't eaten.

Just the raw formatting of that could be improved, by cuddling up ELSE IF from separate lines.

Aren't there some END tokens missing? Let me put them in:

  IF card is valid DO
    IF card owner is valid DO
      IF request withdrawal DO
        IF authorization code is valid DO
          query for amount
          IF request <= current balance DO
            IF withdrawal <= available cash DO
              vend currency
              debit account
            ELSE
              message
              terminate session
            END
          ELSE
            message
            terminate session
          END
        ELSE
          message
          terminate session
        END
      ELSE
        IF authorization code is valid DO
          query for amount
          accept envelope through hatch
          credit account
        ELSE
          message
          terminate session
        END
    ELSE
      eat card
    END
  ELSE
    message
  END
Okay, now:

  IF card is valid DO
    IF card owner is valid DO
      IF request withdrawal DO
        IF authorization code is valid DO
          query for amount
          IF request <= current balance DO
            IF withdrawal <= available cash DO
              vend currency
              debit account
            ELSE
              message
              terminate session
            END
          ELSE
            message
            terminate session
          END
        ELSE
          message
          terminate session
        END
      ELSE IF authorization code is valid DO
        query for amount
        accept envelope through hatch
        credit account
      ELSE
        message
        terminate session
      END
    ELSE
      eat card
    END
  ELSE
    message
  END
Okay, just one ELSE IF; not much opportunity for that.
On that topic, I did a somewhat weird thing in C yesterday. I took code like this:

  for (;;) {
    /* original big loop */
  }
and turned it into this:

  if (compatibility_with_old_version) for(;;) {
    /* original big loop: same indentation level! */
  } else for (;;) {
    /* rewritten new loop */
  }
 
I.e.

  if (...) for (...) {

  } else for (...) {

  }
Works for me.