Monadic Vectors

Eric Bailey

Written on 2 December, 2023
Updated on 02 January, 2024
Tags: haskell, monads, mathematics, algebra

The Functor instance for 3-dimensional vectors applies a function f to each basis vector, preserving the structure of the 3-D vector.

instance Functor V3 where
  fmap f (V3 a b c) = V3 (f a) (f b) (f c)
  a <$ _ = V3 a a a
λ> fmap (+1) (V3 1 2 3)
V3 2 3 4

The Applicative instance provides operations to embed pure expressions (pure), and sequence computations and combine their results (<*> and liftA2). N.B. The default definition is:
liftA2 f x y = f <$> x <*> y

instance Applicative V3 where
  pure a = V3 a a a
  V3 a b c <*> V3 d e f = V3 (a d) (b e) (c f)
λ> pure 0 :: V3 Int
V3 0 0 0
λ> V3 (+5) (+3) (+1) <*> V3 1 2 3
V3 6 5 4

Together they enable applying a binary function as follows.

λ> (+) <$> V3 1 2 3 <*> V3 4 5 6
V3 5 7 9

There's also a Monad instance, which enables concise and elegant code.

instance Monad V3 where
  V3 a b c >>= f = V3 a' b' c' where
    V3 a' _ _ = f a
    V3 _ b' _ = f b
    V3 _ _ c' = f c

For example, as part of the Advent of Code puzzle for Day 2 of 2023, one must parse revelations of the form N COLOR where N is a natural number and COLOR is one of red, green, and blue. The tricolor nature of the revelations (and the subsequent computations therewith) lends itself nicely to 3-dimensional vectors.

A naive Parser might look as follows.

revelation :: Parser (V3 Integer)
revelation =
  do
    n <- natural
    V3 n 0 0 <$ string "red"
      <|> V3 0 n 0 <$ string "green"
      <|> V3 0 0 n <$ string "blue"

The Monad instance, however, enables the following.

revelation :: Parser (V3 Integer)
revelation =
  natural >>= \n ->
    for (V3 "red" "green" "blue") $ \color ->
      n <$ string color <|> pure 0

Also delightfully concise is this way of determining which games are possible.

isPossible :: [V3 Integer] -> Bool
isPossible = all (and . liftA2 (>=) (V3 12 13 14))