在 Haskell 中映射 `IO (S (Maybe a))`?

mapping `IO (S (Maybe a))` in Haskell?

基于Data.Typeable cast and Maybe pattern match behavior in Haskell

我有

然后,基本上需要一个函数

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

这个问题的代码非常简单,并且有理由为了我的目的而拥有这样的结构和功能。例如 S {val :: IO a} 实际上持有一个字段值 Data.Vector.Mutable 这就是为什么类型是 IO a.

基本概念只是 map/function-application 值。

如果 Maybe aNothing,我希望代码在 IO 和 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

当然,错误很明显,所以我明白这是怎么发生的,那不是这个问题的范围。

我根本想不通如何用IOMaybe的混合结构来完成预期的类型。你如何解决这个问题?

我不确定你想要达到什么目的。您将 IO 嵌套在另一个 IO(又名 S)中的方式有​​点奇怪。

此外,您的 _map 类型只接受普通居民:

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

注意最后的 b。无法从参数中获得这样的 b,迫使代码始终使用 Nothing :: Maybe b,并进行适当包装。我想这不是你想要做的。

我把 b 改成了 a。然后我们可以测试sMaybeA的结果并据此采取行动。

_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

请注意,虽然可以编译,但它可能与您实际需要的有所不同。

我觉得您对 Haskell 类型系统的担忧源于您在此处展示的具有误导性的 API 设计。您正在尝试嵌套您的类型而不是组合它们。我相信,在编写 Haskell 时,您会发现 Haskell 类型之间的组合非常好。您不必退回到幼稚但丑陋的嵌套。

像这样的东西怎么样?

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

没有嵌套 IO,因为您 永远不会 需要嵌套 IO(除非您正在使用 STM)。您可以简单地组合您的 IOs,如果其中之一仅用于可变数组并不重要。

我还冒昧地将 Maybe a 嵌入到 S 中,因为您的 API 似乎就是这么做的。然而,你很可能会把它拿出来继续 Functor/Applicative/Monad 实例,并继续保持平坦的 IO 层次结构。