如何解开嵌套在不同级别的多个Maybes?

时间:2017-01-06 21:53:15

标签: haskell

在Haskell中使用Maybe似乎非常困难。在许多令人沮丧的编译错误之后,我能够实现我需要的功能,但它仍然完全混乱,我不知道如何改进它。

我需要:

  • 将多个嵌套... Maybe提取为一个,最终Maybe ...
  • a -> b -> IO ()Just aJust b或*(*)
  • IO

以下是移除a -> b -> IO ()部分的示例。我需要(a,b) -> IO (),而不是mapM_,但我无法弄清楚如何传递两个参数(我只能import Network.URI type URL = String type Prefix = String fubar :: String -> Maybe (Prefix, URL) fubar url = case parseURI url of Just u -> (flip (,) $ url) <$> (fmap ((uriScheme u ++) "//" ++ ) ((uriRegName <$> uriAuthority u))) _ -> Nothing 只有一个参数)。

> fubar "https://hackage.haskell.org/package/base-4.9.0.0/docs/src/Data.Foldable.html#mapM"
Just ("https://hackage.haskell.org"
     ,"https://hackage.haskell.org/package/base-4.9.0.0/docs/src/Data.Foldable.html#mapM"
     )

结果:

{{1}}

(*)打印无法解析错误的内容

3 个答案:

答案 0 :(得分:11)

使用do表示法写的非常简单:

fubar :: String -> Maybe (Prefix, URL)
fubar url = do
  u <- parseURI url
  scheme <- uriScheme u
  domain <- uriRegName <$> uriAuthority u
  return $ (scheme ++ "//" ++ domain, url)

一般的Monads(特别是可能)都是将m (m a)合并到m a。每个<-绑定都是对>>=的调用的替代语法,如果它看到Nothing,则负责中止的操作员,以及以其他方式打开Just for you。

答案 1 :(得分:5)

首先请注意,您只需使用fmap堆叠多个α <$> (fmap β (γ <$> uriAuthority u))。这可以(仿函数法则!)重写α . β . γ <$> uriAuthority u,即

{-# LANGUAGE TupleSections #-}

   ...
    Just u -> (,url) . ((uriScheme u++"//") ++ ) . uriRegName <$> uriAuthority u

实际上保持图层分离的可读性可能更好,但是你也应该按照合金建议给它们命名。

此外,更强烈:

  

将多个嵌套M提取为一个,最终M

嗯,听起来像是monad,不是吗?

fubar url = do
   u <- parseURI url
   (,url) . ((uriScheme u++"//") ++ ) . uriRegName <$> uriAuthority u

答案 2 :(得分:3)

我并不完全清楚你所问的是什么,但我会尽力回答你提出的问题。

要将多个嵌套Maybe提取到单个最终Maybe中,需要Maybe的monad-nature(也是应用性质)来处理。具体如何做取决于它们是如何嵌套的。

最简单的例子:

Control.Monad.join :: (Monad m) => m (m a) -> m a
-- thus 
Control.Monad.join :: Maybe (Maybe a) -> Maybe a

元组:

squishTuple :: (Maybe a, Maybe b) -> Maybe (a,b)
squishTuple (ma, mb) = do  -- do in Maybe monad
    a <- ma
    b <- mb
    return (a,b)

-- or
squishTuple (ma, mb) = liftA2 (,) ma mb

清单:

sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)
-- thus
sequenceA :: [Maybe a] -> Maybe [a]
-- (where t = [], f = Maybe)

通过组合这些结构并遵循类型,可以平整其他结构。例如:

flattenComplexThing :: (Maybe a, [Maybe (Maybe b)]) -> Maybe (a, [b])
flattenComplexThing (ma, mbs) = do
    a <- ma
    bs <- (join . fmap sequenceA . sequenceA) mbs
    return (a, bs)

join . fmap sequenceA . sequenceA行有点复杂,需要一些时间来了解如何构建这样的东西。我的大脑以一种非常类型导向的方式工作(从右到左阅读构图):

[Maybe (Maybe b)]
      |
   sequenceA :: [Maybe _] -> Maybe [_]
      ↓
Maybe [Maybe b]
      |
    -- sequenceA :: [Maybe b] -> Maybe [b]
    -- fmap f  makes the function f work "inside" the Maybe, so
   fmap sequenceA :: Maybe [Maybe b] -> Maybe (Maybe [b])
      ↓
Maybe (Maybe [b])
      |
   join :: Maybe (Maybe _) -> Maybe _
      ↓
Maybe [b]

关于第二个问题,如果你有a -> b -> IO ()Maybe a,如何做Maybe b,假设你根本不想在这个案件中采取行动任何一个都是Nothing,你只做一些体操:

conditional :: (a -> IO ()) -> Maybe a -> IO ()
conditional = maybe (return ())

conditional2 :: (a -> b -> IO ()) -> Maybe a -> Maybe b -> IO ()
conditional2 f ma mb = conditional (uncurry f) (liftA2 (,) ma mb)

我再一次在脑海中找到了conditional2主要以类型为导向的方式。

开发类型体操需要一些时间,但随后开始变得非常有趣。为了使这样的代码可读,使用辅助函数是很重要的,例如,上面conditional,并将它们命名为好(这可以说是conditional :-)。您将逐渐熟悉标准库的帮助程序。这里没有灵丹妙药,你只需要习惯它 - 它是一种语言。使用它,努力清晰,如果有些太难看,请尽量使它更漂亮。并提出更具体的问题: - )