Applicative Arithmetic

Posted on September 25, 2017

Safer Arithmetic

There are a couple partial functions in the Haskell Prelude which people seem to agree shouldn’t be there. head , for example, will throw an error on an empty list. Most seem to agree that it should work something more like this:

head :: Foldable f => f a -> Maybe a f a head = foldr ( const . Just ) Nothing

There are other examples, like last , !! , etc.

One which people don’t agree on, however, is division by zero. In the current Prelude, the following will throw an error:

1 / 0

The “safe” version might have a signature like this:

(/) :: Fractional a => a -> a -> Maybe a

However, this turns out to be quite a headache for writing code generally. So the default is the (somewhat) unsafe version.

Is there a way to introduce a safer version without much overhead, so the programmer is given the option? Of course! With some newtype magic, it’s pretty simple to write a wrapper which catches division by zero in some arbitrary monad:

newtype AppNum f a = AppNum f a { runAppNum :: f a f a } deriving ( Functor , Applicative , Monad , Alternative , Show , Eq , MonadFail ) instance ( Num a, Applicative f) => a,f) Num ( AppNum f a) where f a) abs = fmap abs signum = fmap signum ( + ) = liftA2 ( + ) liftA2 ( ( * ) = liftA2 ( * ) liftA2 ( ( - ) = liftA2 ( - ) liftA2 ( negate = fmap negate fromInteger = pure . fromInteger instance ( Fractional a, MonadFail f, Eq a) => a,f,a) Fractional ( AppNum f a) where f a) fromRational = pure . fromRational / ys = xsys >>= ys \ case 0 -> fail "divide by zero" y -> fmap ( / y) xs y) xs

I’m using the -XLambdaCase extension and MonadFail here.

Free Applicatives

You’ll notice that you only need Applicative for most of the arithmetic operations above. In fact, you only need Monad when you want to examine the contents of f . Using that fact, we can manipulate expression trees using the free applicative from the free package. Say, for instance, we want to have free variables in our expressions. Using Either , it’s pretty easy:

type WithVars = AppNum ( Ap ( Either String )) Integer )) var :: String -> WithVars = AppNum . liftAp . Left varliftAp

We can collect the free variables from an expression:

vars :: WithVars -> [ String ] = runAp_ ( either pure ( const [])) . runAppNum varsrunAp_ ([]))runAppNum x = 1 :: WithVars y = var "y" var z = var "z" var + y + z) -- ["y","z"] vars (xz)

If we want to sub in, though, we’re going to run into a problem: we can’t just pass in a Map String Integer because you’re able to construct values like this:

bad :: AppNum ( Ap ( Either String )) ( Integer -> Integer -> Integer ) )) ( = AppNum (liftAp ( Left "oh noes" )) bad(liftAp ())

We’d need to pass in a Map String (Integer -> Integer -> Integer) as well; in fact you’d need a map for every possible type. Which isn’t feasible.

GADTs

Luckily, we can constrain the types of variables in our expression so that they’re always Integer , using a GADT:

data Variable a where Constant :: a -> Variable a Variable :: String -> Variable Integer

The type above seems useless on its own: it doesn’t have a Functor instance, never mind an Applicative , so how can it fit into AppNum ?

The magic comes from the free applicative, which converts any type of kind Type -> Type into an applicative. With that in mind, we can change around the previous code:

type WithVars = AppNum ( Ap Variable ) Integer var :: String -> WithVars = AppNum . liftAp . Variable varliftAp vars :: WithVars -> [ String ] = runAp_ f . runAppNum varsrunAp_ frunAppNum where f :: Variable a -> [ String ] Constant _) = [] f (_)[] Variable s) = [s] f (s)[s]

And write the function to sub in for us:

variableA :: Applicative f => ( String -> f Integer ) -> Variable a -> f a f a Constant x) = pure x variableA _ (x) Variable s) = f s variableA f (s)f s variable :: ( String -> Integer ) -> Variable a -> a Constant x) = x variable _ (x) Variable s) = f s variable f (s)f s replace :: Map String Integer -> WithVars -> Integer = runAp (variable (m Map.! )) . runAppNum replace mrunAp (variable (m))runAppNum "z" , 2 ), ( "y" , 3 )]) (x + y + z) replace (Map.fromList [(), ()]) (xz) -- 6

Accumulation

This will fail if a free variable isn’t present in the map, unfortunately. To fix it, we could use Either instead of Identity :

replace :: Map String Integer -> WithVars -> Either String Integer = replace m runAp $ (variableA -> \s maybe ( Left s) Right (Map.lookup s m)) . s)(Map.lookup s m)) runAppNum

But this only gives us the first missing variable encountered. We’d like to get back all of the missing variables, ideally: accumulating the Left s. Either doesn’t accumulate values, as if it did it would break the monad laws.

There’s no issue with the applicative laws, though, which is why the validation package provides a non-monadic either-like type, which we can use here.

replace :: Map String Integer -> WithVars -> AccValidation [ String ] Integer = replace m runAp $ (variableA -> \s maybe ( AccFailure [s]) pure (Map.lookup s m)) . [s])(Map.lookup s m)) runAppNum + y + z) replace (Map.fromList []) (xz) -- AccFailure ["y","z"]

Other uses

There are a bunch more applicatives you could use instead of Either . Using lists, for instance, you could calculate the possible outcomes from a range of inputs:

range :: WithVars -> [ Integer ] range = runAp (variable ( const [ 1 .. 3 ])) . runAppNum runAp (variable (]))runAppNum range (x + y + z) (xz) -- [3,4,5,4,5,6,5,6,7]

Or you could ask the user for input:

query :: WithVars -> IO Integer = runAp (variable f) . runAppNum queryrunAp (variable f)runAppNum where = do f s putStr "Input a value for " putStrLn s fmap read getLine

Finally, and this one’s a bit exotic, you could examine every variable in turn, with defaults for the others:

zygo :: ( forall x . f x -> x) f xx) -> ( forall x . f x -> (x -> a) -> b) f x(xa)b) -> Ap f a f a -> [b] [b] l :: forall x . f x -> x) ( c :: forall x . f x -> (x -> a) -> b) = zygo (f xx) (f x(xa)b) fst . go id go where go :: forall c . (c -> a) -> Ap f c -> ([b], c) (ca)f c([b], c) Pure x) = ([], x) go _ (x)([], x) Ap x f) = (c x (k . ls) : xs, ls lx) go k (x f)(c x (kls)xs, ls lx) where = go (k . ( $ lx)) f (xs,ls)go (klx)) f = l x lxl x examineEach :: WithVars -> [ Integer -> Integer ] = zygo (variable ( const 1 )) g . runAppNum examineEachzygo (variable ()) grunAppNum where g :: Variable a -> (a -> b) -> Integer -> b (ab) Constant x) rhs _ = rhs x g (x) rhs _rhs x Variable _) rhs i = rhs i g (_) rhs irhs i

This produces a list of functions which are equivalent to subbing in for each variable with the rest set to 1.