简体   繁体   中英

Parameterized Maybe monad

I'm making a mess with Monad, ReaderT, ... to perform a "simple?" behavior.

I want to intersperse a test function into Maybe transformation ( Maybe or another personalized monad).

Exactly, I want avoid t calls, creating some kind of monad (monad, I think) on

doCalculus :: (Int -> Bool) -> Int -> Maybe Int
doCalculus f a = do
  b <- t $ a + 1
  c <- t $ 2 * b
  d <- t $ a + b + c
  return d
  where t = if f n then Just n else Nothing

example

test :: Int -> Bool
test n = not (n `elem` [3, 7, 9])

*Main> doCalculus test 2
Nothing
*Main> doCalculus test 3
Just 15
*Main>

I'm trying to do some monad like ReaderT to perform some like

runMaybeTest doCalculus test

to use as

doCalculus :: Int -> Maybe' Int
doCalculus a = do
  b <- a + 1
  c <- 2 * b
  d <- a + b + c
  return d

perform = runMaybe' doCalculus test

but I can't.

(Of course, Int type would be generic into monad)

Thank you for any tip!

=== UPDATE 1 ===

I can do it! :) ... but isn't practical (I think) :(

I had adapted a fantastic Eric Kidd post

import Prelude hiding (Just, Nothing, return, (>>=))

class Tester a where
  test :: a -> Bool
  test _ = True

data MMaybe a = Nothing | Just a deriving (Eq, Show)

class Monad1 m a where
  return :: a -> m a
  fail :: String -> m a

class (Monad1 m a, Monad1 m b) => Monad2 m a b where
  (>>=) :: m a -> (a -> m b) -> m b

instance (Tester a) => Monad1 MMaybe a where
  return = Just
  fail _ = Nothing

instance (Tester a, Tester b) => Monad2 MMaybe a b where
  Nothing >>= _ = Nothing
  (Just x) >>= f = if test x then f x else Nothing

instance Tester Int where
  test n = not $ n `mod` 2 == 0 && n `mod` 3 == 0

test1 :: Int -> MMaybe Int
test1 n =
  return n >>= \a ->
  return (a + 3) >>= \b ->
  return (a + b)

test2 = map test1 [1..20]

possible (important) problems are:

  1. is a usable monad?
  2. where is do notation?
  3. only works defining test function into a unique type (new testing functions require new types)

but I can wrap test function into pseudo-monad... (it's something)

It looks like you want to (a) sequence some transformations and (b) short-circuit predicate failure at various stages. This whole process is parametric over the "contained" type (which is Int here) and the predicate.

Let's dive in.

I

The primary effect we're controlling here is failure, so Maybe is a great place to start. It's Monad instance lets us compose various Maybe -producing computations.

-- some pure computations
f, g, h :: a -> a

-- ... lifted and sequenced!
may :: a -> Maybe a
may = (return . f) >=> (return . g) >=> (return . h)

This is a very ceremonial way of writing (h . g . f) since we're just using completely general "monadic" (really, Kleisli ) composition and no special effects.

II.

Given a predicate p :: a -> Bool , we can start to fail. The first way to do this would be to use Maybe 's MonadPlus instance and guard :: MonadPlus m => Bool -> m () .

\a -> do x <- return (f a)
         guard (p x)
         y <- return (g x)
         guard (p y)
         z <- return (h y)
         guard (p z)
         return z

But we've obviously got a fairly repeated pattern going on here---at every "composition boundary" of pure functions we perform our predicate and maybe fail. This is a strong commingling of Reader -like and Maybe -like effects just like you thought, but it doesn't have quite the same Monad ic semantics as either of those or their stack. Can we capture it some other way?

III.

Well, let's try wrapping them up.

newtype OurMonad a = OM { getOM :: MaybeT (Reader (a -> Bool)) a }

Now OurMonad is a newtype around the monad transformer stack including Reader and Maybe . We'll be able to take advantage of that in order to write a highly general "run" function, runOurMonad :: (a -> Bool) -> OurMonad a -> Maybe a .

Or, rather, it sort of feels like we could, right? I want to argue that we can't, actually. The reason is that in order to write a Monad instance we have to have a Functor instance* which means that given any function mapping a -> b we need to be able to map OM a -> OM b . The problem is that we don't generally know how to generalize our predicate! I know no way to write a function (a -> b) -> (a -> Bool) -> b -> Bool unless we also have a function b -> a **.

So we're stuck trying to generalize this to a Monad . I don't think it is one.

IV.

But your example didn't actually need the full generality of Monad—we know all of our pure transformations are Endo , ie of type a -> a . That's one way our single predicate could be sufficient! We can take advantage of the type homogeneity to write a list of our Endo s as [a -> a] . So let's just define what we need a little more directly as a fold over that list:

stepGuarded :: (a -> Bool) -> a -> [a -> a] -> Maybe a
stepGuarded pred = foldM $ \a f -> mfilter pred (return $ f a)

stepGuarded (`elem` [3, 7, 9]) 3 [ (+1), (*2) ] 
-- Nothing

stepGuarded (`elem` [4, 8, 9]) 3 [ (+1), (*2) ]
-- Just 8

* nb Not technically true, but theoretically and practically if we can't write a Functor instance then we'll get stuck writing a Monad instance, too.

** Technically, this is still a categorical Functor , it's just contravariant whereas Functor and Monad assume the functor is covariant. We can generalize all of this to have functors-on-isomorphisms, Edward Kmett calls these ExFunctor s I think and you get to define xmap :: (a -> b) -> (b -> a) -> fa -> fb instead, which is nice.

I think what you need to do is use "let" in your do blocks.

doCalculus :: Int -> Maybe Int
doCalculus a = do
  let b = a + 1
  let c = 2 * b
  let d = a + b + c
  return d

or

doCalculus :: Int -> Maybe Int
doCalculus a = Just d where
  b = a + 1
  c = 2 * b
  d = a + b + c

and skip the do notation all together.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM