7. Do-notation

Note

The approach presented here uses the dreadful RebindableSyntax GHC extension. We recommend that you use VEither instead.

We can use do-notation with Variant as we would with other sum types such as Maybe or Either. However, as we can’t have a Monad instance for Variant, we rely on the RebindableSyntax extension to mimic it.

The leftmost type is extracted from the Variant with >>= (or x <- myVariant with do-notation syntax). Variant types are concatenated on the left.

Function foo in the following example composes functions returning Variants by using do-notation:

{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RebindableSyntax #-}

import Haskus.Utils.Variant
import Haskus.Utils.Variant.Syntax

import Prelude hiding (head,lookup,(>>=),(>>),return)
import qualified Prelude
import Text.Read

foo :: String -> V '[Integer, ParseError, LookupError Char, HeadError]
foo str = do
   c <- head str
   r <- lookup c codeMap
   parse (r ++ tail str)

   where
      codeMap :: [(Char, String)]
      codeMap = [ ('x', "0x")
                , ('d', "")
                ]


data ParseError = ParseError deriving Show

parse :: String -> V '[Integer,ParseError]
parse s = case readMaybe s of
   Just i  -> V @Integer i                -- we use the `V` pattern to index
   Nothing -> V ParseError                -- the Variant by type

data HeadError = ListWasEmpty deriving Show

head :: [a] -> V '[a,HeadError]
head []    = toVariantAt @1 ListWasEmpty  -- we can't index the Variant by
head (x:_) = toVariantAt @0 x             -- type because `a` is ambiguous,
                                          -- so we do it by index explicitly

data LookupError k = KeyWasNotPresent k deriving Show

lookup :: Eq k => k -> [(k,v)] -> V '[v,LookupError k]
lookup k vs = case Prelude.lookup k vs of
   Just v  -> toVariantAt @0 v            -- ditto
   Nothing -> toVariantAt @1 (KeyWasNotPresent k)

Test:

> foo "d10"
V @Integer 10

> foo "x10"
V @Integer 16

> foo "u10"
V @(LookupError Char) (KeyWasNotPresent 'u')

> foo ""
V @HeadError ListWasEmpty

> foo "d10X"
V @ParseError ParseError