0
votes

In an effort to understand monads better, I'm attempting to write my own. I'm starting with some non-monadic code, and could use some help translating it into a monad.

Basic idea for this contrived example: for each integer result of a computation, I'd like to track if that integer is even or odd. For example, in 4 + 5 = 9, we might return (9, Odd).

I'd like to be able to chain/compose the calculations with >>=. For example:

return 1 >>= (+2) >>= (+5) >>= (+7) =result=> (15, Odd)

Right now, I have the following non-monadic code:

data Quality = Odd | Even deriving Show

qual :: Integer -> Quality
qual x = case odd x of
             True -> Odd
             _ -> Even

type Qualifier = (Integer, Quality)

mkQ :: Integer -> Qualifier
mkQ x = (x, qual x)

plusQ :: Qualifier -> Qualifier -> Qualifier
plusQ (x, _) (y, _) = (x+y, qual (x+y))

chain = plusQ (mkQ 7) . plusQ (mkQ 5) . plusQ (mkQ 2)

What are some ways I can translate the above code into a monad? What are some of the patterns I should look for, and what are common translation patterns for them?

Many thanks in advance!

2
This does not easily admit a monadic translation. Monads are parameterized by a type, and make sense for any type. Yor computation only makes sense for intrgers.n. 1.8e9-where's-my-share m.
It seems like you're trying to model state transformations / actions, where your state in this case is the Quality. You'll want to look at the State Monad, where your state monad is parameterized by Quality and your operations (+1) would turn into state actions.aaronlevin
@n.m. Good point, and one that I was beginning to see and wonder about. What might be a better example of a simple, generic process that would fit an exercise like this?Vic Acid
@weirdcanada I was wondering about either using State or Writer monads for this... Writer because the Even/Odd aspect seemed almost like logging. But I was really trying to write my own monad in order to understand the inner and outer workings of monads better. Any ideas?Vic Acid
One simple monad represents computations that may end in an error. Described everywhere, but it's still entertaining to come up with your own implementation.n. 1.8e9-where's-my-share m.

2 Answers

3
votes

I think what you actually want is a Num instance for Qualified:

data Qualified = Qualified { isEven :: Bool, value :: Integer }

instance Num Qualified where
    (Qualified e1 n1) + (Qualified e2 n2) = Qualified e (n1 + n2)
      where
        e = (e1 && e2) || (not e1 && not e2)

    (Qualified e1 n1) * (Qualified e2 n2) = Qualified (e1 || e2) (n1 * n2)

    abs (Qualified e n) = Qualified e (abs n)

    signum (Qualified e n) = Qualified e (signum n)

    fromInteger n = Qualified (even n) n

This lets you manipulate Qualified numbers directly using math operators:

>>> let a = fromInteger 3 :: Qualified
>>> let b = fromInteger 4 :: Qualified
>>> a
Qualified {isEven = False, value = 3}
>>> b
Qualified {isEven = True, value = 4}
>>> a + b
Qualified {isEven = False, value = 7}
>>> a * b
Qualified {isEven = True, value = 12}
0
votes

Lots of learning from this one. Many thanks to the commentors and answers for your time and guidance!

To summarize:

Solution: As @n.m. and others commented, there isn't a good monad translation for this example because my original model isn't type-generic. Monads are best for type-generic computation patterns. Good examples given include the Maybe monad for computations which may fail, and State monad for storing and carrying along accessory state information through a computation chain.

As an alternate solution, @GabrielGonzalez offered a great solution using type instancing. This keeps the inherent type-specificity of my original model, but broadens its interface to support more of the Num type class interface and clean up the functional interactions.

Next steps: As @weirdcanada and others recommended, I think I'll go play with the State monad and see how I can apply it to this particular example. Then I may try my hand at a custom definition of Maybe as @n.m. recommended.

Again, many thanks to those who commented and responded!