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 Answers 2
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}
5 Comments
Num instance, but I think the broader goal is that the OP wants to learn and understand monads.State monad is pretty simple, if you want to learn about monads, define your own State monad and then maybe try to write your own parser using your new monad.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!
Quality. You'll want to look at the State Monad, where your state monad is parameterized byQualityand your operations(+1)would turn into state actions.