Home > Enterprise >  mapping `IO (S (Maybe a))` in Haskell?
mapping `IO (S (Maybe a))` in Haskell?

Time:02-27

Based on Data.Typeable cast and Maybe pattern match behavior in Haskell

I have

  • a structure: data S a = S {val :: IO a}
  • a structure: S (Maybe a))
  • a structure: IO (S (Maybe a)))
  • a function : Maybe a -> IO (S (Maybe a))

then, basically need a function

_map :: (Maybe a -> IO (S (Maybe a))) -> S (Maybe a) -> IO (S (Maybe b))

The code is pretty simplified for this question, and there is a reason to have the structures and function like this for my purpose. For instance S {val :: IO a} actually hold a field value of Data.Vector.Mutable that is why the type is IO a.

The basic concept is simply map/function-application on the value.

In the case of Maybe a is Nothing, I want the code behaves nothing on IO and return IO (S (Nothing)).

data S a = S
  { val :: IO a
  }

_val :: S (Maybe a) -> IO (Maybe a)
_val = \sMaybeA -> val sMaybeA

_map :: (Maybe a -> IO (S (Maybe b))) -> S (Maybe a) -> IO (S (Maybe b))
_map = \f -> \sMaybeA -> do
  val <- sMaybeA |> _val
  sMaybeB <- val >>= f
             -- Expected type: IO (S (Maybe a))
             --   Actual type: Maybe (S (Maybe a))
  pure sMaybeB
  -- another obvious error because sMaybeB has the error

Of course, the error is obvious so I understand how this occurs, and that is not the scope of this quesion.

I simply can't figure out how to accomplish the expected type with the mixed structure of IO and Maybe. How can you fix this out?

CodePudding user response:

I'm not sure about what you are trying to achieve. The way you nest IO within another IO (aka S) is a bit strange.

Also, your type for _map only admits trivial inhabitants:

_map :: (Maybe a -> IO (S (Maybe a))) -> S (Maybe a) -> IO (S (Maybe b))

Note the b at the very end. There is no way to obtain such a b from the arguments, forcing the code to always use Nothing :: Maybe b, suitably wrapped. I guess this is not what you want to do.

I changed the b into an a. Then we can test the result of sMaybeA and act accordingly.

_map :: (Maybe a -> IO (S (Maybe a))) -> S (Maybe a) -> IO (S (Maybe a))
_map f sMaybeA = do
  v <- val sMaybeA
  case v of
     Nothing -> return (S (return Nothing))
     Just _  -> f v

Note that, while this compiles, it could be something different from what you actually need.

CodePudding user response:

I feel like your concerns about Haskell's type system stem from the misleading API design you've showcased here. You're trying to nest your types rather than compose them. I believe that, while writing Haskell, you'll find that Haskell types compose very well with each other. You don't have to fallback to naive yet ugly nesting.

What about something like this instead?

newtype S a = S { val :: IO (Maybe a) }

instance Functor S where
  fmap f = S . fmap (fmap f) . val

instance Applicative S where
  pure = S . pure . pure
  S f <*> S v = S $ liftA2 (<*>) f v

instance Monad S where
  S ma >>= f = S $ do
    maybex <- ma
    case maybex of
      Nothing -> pure Nothing
      Just a -> val $ f a

weirdMap :: (a -> S b) -> S a -> S b
weirdMap f sa = do
  value <- sa
  f value

There's no nested IO, because you never need nested IO (unless you're working with STM). You can simply combine your IOs, doesn't matter if one of them is just for a mutable array.

I've also taken the liberty to embed the Maybe a within S, as it seems like that's what your API does anyway. However, you may very well take it out and still proceed with the Functor/Applicative/Monad instances, and continue to keep a flat IO hierarchy.

  • Related