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:
- Every time we want to read from our context, we use
⟜Fto pushF(context)to the top of the effective stack. - To manipulate the effective stack any other way, we use
⊙to dip past the context. - 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.