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!