Tech News
← Back to articles

Extreme branchless: Expr without GADTs or sum-types

read original related products more articles

In the previous log we have continued my branchless journey, based on Church encoding, having this type definition:

newtype CExpr a = CExpr { runCExpr :: forall r . ( a -> r a ) -> ( CExpr Int -> CExpr Int -> r Int ) -> ( CExpr Int -> CExpr Int -> r Bool ) -> r a }

As a reminder, I did so to avoid relying on extensions, but, to be fair, it takes some efforts to get use to that notation.

After a second thought, well, maybe more a 20th, there is a more mainstream way to tackle it.

However, it involves embedded all operations in a product-type, leaving the implementations open, as it is done in OOP, as follows:

data FExpr a = FExpr { feval :: a , fprint :: String }

Thanks to GHC defaults, nothing is evaluated until it is needed.

We can define operations one by one as follows:

fval :: ( Show a ) => a -> FExpr a fval x = FExpr { feval = x , fprint = show x } fadd :: FExpr Int -> FExpr Int -> FExpr Int fadd x y = FExpr { feval = x . feval + y . feval , fprint = " ( " <> x . fprint <> " ) + ( " <> y . fprint <> " ) " } feq :: FExpr Int -> FExpr Int -> FExpr Bool feq x y = FExpr { feval = x . feval == y . feval , fprint = " ( " <> x . fprint <> " ) == ( " <> y . fprint <> " ) " }

It's clearly more readable, and it makes adding new kind of expression cheap, but adding new operation costly.

... continue reading