/ factor

Solving Embarrassingly Simple Problems (in Factor)

This is an old draft that I discovered when going through my gists. Everything I wrote then is still true, so please enjoy!

This Article has struck a chord with me. I'm not an Erlang-er, but the style he describes of "writing many tiny functions" strongly reminds me of why I've come to love Factor.

Drink Beer when Delicious

  • "good" or "well written" Erlang functions are very Factor-like
  • Tiny functions are easy to understand.

Take the first example: drinking beer when it's delicious.

This mess:

if
deliciousness(Beer) >= ?EXCELLENT ->
    drink(Beer);
true ->
    obstain
end

Translates into this, when refactored:

maybe_drink(if_standard_met(deliciousness(Beer), ?EXCELLENT), Beer)

The key realization here is to formalize the "deliciousness comparison" in if_standard_met. This is the same thought process that happens when 'factoring' a word in Factor. For example, this is an ugly but direct translation of the above Erlang if:

: maybe_drink ( beer -- )
   dup deliciousness>> ?EXCELLENT >=
   [ drink ] [ abstain ] if ;

Thanks to the terse-ness of Factor's syntax, the code winds up being very short -- just 58 characters! -- but due to stack shuffling & symbol noise (>>, ?, []) it's downright intimidating. And again, the intent of the code is lost somewhere along the way.

What we want is this:

: maybe_drink ( beer -- beer' )
  beer_delicious? [ drink! ] when ;

Wow! How clear! I read that as, "When the beer is delicious, drink!." A motto to live by, almost as good as the classic Forth bumper sticker FORTH LOVE? IF HONK THEN

J'proteste!

"But," I hear you protest, "this will lead to half a dozen tiny functions." "Precisely!" It leads to several small and obviously correct functions ("words" in Factor parlance).

How do we tell if a beer is delicious? Get the 'deliciousness' of the beer and compare it to the ?EXCELLENT standard:

: beer_delicious? ( beer -- beer ? )   #! Is this beer delicious?
  ... deliciousness ?EXCELLENT if_standard_met ;

How do we compare a score against its standard? For now, greater-than-or-equal-to is fine.

: if_standard_met ( score goal -- ? ) >= ;	

>= is an ordinary word in Factor, a bit like a function in other languages.

How do we drink a beer? Let's represent "consumed" beer with a boolean flag. So then, to drink a beer:

: drink! ( beer -- beer' ) t >>consumed ;  #! set the 'consumed' flag to true

Each is so simple that I can, at a glance, see that they do the right thing.

Implicit left-application

One feature of Factor (and of concatenative languages in general) is implicit left-application, such that:

 functional       concatenative
 f(g(X))          -> X g f
 add(1,2)         -> 1 2 add

Implicit left-application leads to visually uncluttered code. And it largely eliminates mental juggling of nesting levels.

It does, however, lead to mental stack juggling and a smug sense of "my code is so short!". And sometimes I need to explicitly deal with the stack using "shuffle words" such as dup and swap:

 functional         concatenive
 f(X, X)            -> X dup f
 f(g(X), X)         -> X dup g swap f

Heavy use of dup and swap is a code smell, but, in their correct application, they serve to enforce sane stack flow between words.

That means we can translate the 'refactored' Erlang version to:

 maybe_drink(if_standard_met(deliciousness(B), ?EXCELLENT), B)  ->
     B dup deliciousness ?EXCELLENT if_standard_met maybe_drink

In practice, dup is as easy to visually parse as f(X, X). The direct translation of maybe_drink is not pretty to look at, which is why we factor the definition (har har):

: beer_delicious? ( beer -- beer ? )
  dup
  deliciousness ?EXCELLENT standard_met? ;

: maybe_drink ( beer -- beer' )
  beer_delicious? [ drink! ] when ;

I admit: I cheated a little and snuck dup into beer_delicious? to keep maybe_drink short, clear, and beautiful. Considering the original, this makes sense:

maybe_drink(f(B), B)

beer_delicious? provides both the flag and argument for when conditional. Factor's stack-effect declaration makes that explicitly clear:

: beer_delicious? ( beer -- beer ? ) ...
   #! Accepts a beer, and returns the same beer with a flag ('?')

Those stack parameters can be named anything, but convention is that ? means a boolean value. Another convention is appending a tick or "prime" indicator when the word alters the object:

: maybe_drink ( beer -- beer' ) ...
  #! This accepts a beer and returns a modified beer.

Read that as "accepts beer, returns beer prime" — beer' is either the beer, consumed, or the original beer, untouched.

(Because this word can thirstily modify the beer there's one more convention to follow: it should be called maybe_drink!)

Summary

  • Small is beautiful.
  • Small is easy to understand.
  • Small definitions lead to clarity of thought.

And above all:

  • When the beer is delicious, drink.