在 Aeson 解析器中展平 MonadPlus

Flatten MonadPlus inside an Aeson Parser

我不确定我是否在这里树错了树,但我有一个 Aeson FromJSON 定义看起来相当笨重,我想知道它是否可以变成更简洁的东西。如果URI的嵌套解析失败,我想短路整个对象的解析。

data Link = Link { link :: URI
                 , tags :: [String]
                 } deriving (Show, Typeable, Eq)

instance FromJSON Link where
    parseJSON :: Value -> Parser Link
    parseJSON (Object o) = do
        linkStr <- o .: "link"
        tags' <- o .: "tags"

        case parseURI linkStr of
            Just l -> return $ Link l tags'
            Nothing -> mzero

    parseJSON _ = mzero

parseURI 的类型是 parseURI :: String -> Maybe URIMaybeParser 都有 MonadPlus 个实例。有没有办法直接把两者组合起来,去掉最后丑陋的case语句?

Applicative 解析器通常更简洁,您可以使用 maybe mzero returnNothing 转换为 mzero 来组合 parseURI 的结果。

instance FromJSON Link where
    parseJSON :: Value -> Parser Link
    parseJSON (Object o) = Link
        <$> (maybe mzero return . parseURI =<< o .: "link")
        <*> o .: "tags"

    parseJSON _ = mzero

模式匹配有效,但这仅在 do 符号内部有效 >>= 由于正在进行的额外脱糖:

instance FromJSON Link where
    parseJSON (Object o) = do
        Just link' <- o .: "link"
        tags'      <- o .: "tags"
        return $ Link link' tags'
    parseJSON _ = mzero

> -- Note that I used link :: String for my testing instead
> decode "{\"link\": \"test\", \"tags\": []}" :: Maybe Link
Just (Link {link = "test", tags=[]})
> decode "{\"tags\": []}" :: Maybe Link
Nothing

这里发生的事情是 <- 左侧的模式匹配失败正在调用 fail。查看 Parsersource 告诉我 fail 正在调用 failDesc,它也被 mzero 的实现使用,所以在这种情况下你安全了。一般来说,它只是调用 fail,它可以根据 monad 做任意数量的事情,但是对于 Parser 我认为这是有道理的。

但是,@shang 的答案肯定更好,因为它不依赖于隐式行为。