5
\$\begingroup\$

I want to create a monad which will prevent more than 10 instances of recursion (which I am implementing by preventing more than 10 instances of >>=). My >>= definition seems kind of long and messy though, so I am wondering if there is a better way to do it.

I am including a factorial function which uses this monad as an example of how it could be used.

import Control.Monad.State
type LimitState a = (State Int (Either String a))
newtype Limiter a = Limiter {getState :: (LimitState a)}
instance Monad Limiter where
 return x = Limiter $ return $ Right x
 (Limiter state) >>= f = Limiter $ 
 do
 increment
 newCount <- get
 if newCount >= 10 
 then return $ Left "too many recursions"
 else getNextValue
 where 
 increment = do
 count <- get
 put (count + 1)
 getNextValue = do
 value <- state
 getState $ case value of
 (Left message) -> Limiter $ return $ Left message
 (Right v) -> f v
limitedFactorial :: Int -> Limiter Int
limitedFactorial 1 = return 1
limitedFactorial n = do
 recursive <- limitedFactorial (n - 1)
 return $ n * recursive
testLimitedFactorial =
 let (aValue, _) = runState (getState $ limitedFactorial 5) 0
 (uValue, _) = runState (getState $ limitedFactorial 11) 0
 in print aValue >> print uValue 
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Sep 30, 2017 at 0:50
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Sheriff voice: Halt! You are breaking the laws!

Your monad breaks the right- and left-identity law of >>=:

k >>= return = k -- right identity
return a >>= f = f a -- left identity

Suddenly, it makes a big difference whether I call return at the end of my function:

do
 replicateM 9 actions -- fine
do
 k <- replicateM 9 actions
 return k -- fails

That simply breaks to much. Your Monad is also missing its Functor and Applicative instances, which would make it easy to "cheat". And if you implemented a fmap that limits the number of fmaps, you start to break Functor laws. Note that due to the Monad/Functor laws, we should be able to rewrite

limitedFactorial :: Int -> Limiter Int
limitedFactorial 1 = return 1
limitedFactorial n = do
 recursive <- limitedFactorial (n - 1)
 return $ n * recursive

to

limitedFactorial :: Int -> Limiter Int
limitedFactorial 1 = return 1
limitedFactorial n = fmap (n *) $ limitedFactorial (n - 1)

If you were to limit that, you would break fmap id = id immediately.

But even without all of these quirks, at long last, there's nothing preventing the user to use recursion:

example :: Limit Int
example = return 1 -- fine
evil :: Int -> a -> Int
evil x _ = if x > 0 then 1 + evil (x - 1) undefined else 0
do
 liftM (evil 100000000) example -- recursion!

So conceptionally, your Monad is broken, unfortunately.

Further review

  1. Your code uses names from your imports. state is a function defined in Control.Monad.State, so it makes your instance slightly hard to read.
  2. You use increment only once, so it makes sense to inline it.
answered Sep 30, 2017 at 9:04
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.