lynn's house

Reader-style Uiua

Lately I’ve been playing with Uiua, an APL-like programming language that has no local variables on purpose. Instead, you describe how data flows through your function using combinators. One can also think of the language as stack-based, though the official documentation has recently started to discourage this perspective.

I appreciate the die-hard commitment to tacit, point-free programming on an aesthetic level, but on a practical level I find it often gets in the way. I’ve landed on a nice way to make programming in Uiua feel more like a regular stack-based language. We can write functions that keep the top-of-stack fixed to some ambient passed-in context, that we constantly either read parts from, or dip past.

Here’s an example of a quadratic discriminant function, Disc(c,b,a) = b²−4ac, side-by-side with what we might write if Uiua had local variables:

Disc ← (           # Disc(C_B_A) ← (
  ~ {C B A}        #
  ⊙ⁿ₂ ⟜B           #   ⁿ₂ B
  ⊙× ⊙4 ⊙× ⟜A ⟜C   #   × 4 × A C
  ⊙-               #   -
  ◌                #
)

We define the shape of the context using a local data definition ~. This context stays on top, and everything underneath it is the effective stack, which starts empty.

Now here’s how we write our code:

  1. Every time we want to read from our context, we use ⟜F to push F(context) to the top of the effective stack.
  2. To manipulate the effective stack any other way, we use to dip past the context.
  3. At the end, we pop the context, leaving only the effective stack values.

I want to call this style reader style, because in a way, this is like programming in a Reader monad: ⟜f is like asks f and is like pure or fmap or liftA2. For comparison, here is an equivalent Haskell definition of disc, which is also “point-free without really being point-free.”

import Control.Monad.Reader
import Control.Applicative

data Quadratic =
  Quadratic { c, b, a :: Float } deriving (Eq, Show)

disc :: Reader Quadratic Float
disc =
  liftA2 (-)
    (fmap (^2) (asks b))
    (liftA2 (*) (pure 4) $ liftA2 (*) (asks a) (asks c))

This style can and should be mixed freely with regular flavors of Uiua. For example, ⊙× ⊙4 ⊙× ⟜A ⟜C could be written ⟜(×₄×⊃A C), where we use a small, easy-to-read tacit function as our accessor function.


← Back to home