Вот часть объекта JSON, который представляет пользователь:Составление факультативного эсона парсеры
{ "image": { "url": "http://example.com" } }
мне нужно, чтобы разобрать его в тип User
:
data User = User { imgUrl :: Maybe Text }
Наивное решение:
parseJSON (Object o) = User <$> getImgUrl o
where getImgUrl o = (o .:? "image") >>= maybe (return Nothing) (.:? "url")
Но это не намного лучше, чем в этих цепях:
case f m1 of
Nothing -> Nothing
Just m2 -> case f2 m2 of
Nothing -> Nothing
Just m3 -> case f3 m3 ....
, которые часто демонстрируется в «Зачем вам нужна монада» объяснению
Таким образом, мне нужно составить парсер, которые выглядят как (.:? "url") :: Parser (Maybe a)
Я попытался описать эту композицию с comp
функции :
getImgUrl :: Object -> Parser (Maybe Text)
getImgUrl o = o .:? "image" >>= comp (o .:? "url")
comp :: (Monad m) => (a -> m (Maybe b)) -> Maybe a -> m (Maybe b)
comp p Nothing = return Nothing
comp p (Just o) = p o
Пахнет функтор, но fmap
не помогла мне.
Тогда я решил, что композиция должна идти:
getImgUrl :: Object -> Parser (Maybe Text)
getImgUrl = comp2 (.:? "image") (.:? "url") o
-- Maybe should be changed to a matching typeclass
comp2 :: (Monad m) => (a -> m (Maybe b)) -> (b -> m (Maybe c)) -> a -> m (Maybe c)
comp2 = undefined
поиск Hoogle не помог мне, но пробегая через Control.Monad
документы дали мне Kliesli состав, который я не испытывал с. Я вижу некоторое сходство:
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
comp2 :: Monad m => (a -> m (f b)) -> (b -> m (f c)) -> a -> m (f c)
Разница заключается в том, что в составе Maybe
должна быть «разворачивал».
Кажется, что я близок к решению, но все еще не могу его найти. Пожалуйста, дайте мне понять.
[Update]: Я решил, что самое лучшее решение актуальной проблемы, было бы сохранить первоначальную структуру JSON и иметь вложенный тип пользователя:
data User = User { image :: Maybe Image }
data Image = Image { url :: Text }
Это полностью исключает моя проблема, и делает API более совместимым с исходным исходным кодом.
Однако, для теоретических целей было бы здорово увидеть, как можно решить исходную проблему.
Это, по-видимому, разумное решение для реальной проблемы. Однако было бы еще больше увидеть какой-то умопомрачительный общий комбинатор. Если никто не предложит такое, в разумные сроки я помету ваш ответ. – zudov
Также см. Мое обновление выше. О более правильном решении, которое я взял. – zudov
@zudov, очевидно, используя вложенную структуру данных, устраняет эту проблему, но у меня, конечно, есть случаи, когда это не так практично. Для случая, когда вам нужно пересечь дерево, которое может или не может быть, оператор, подобный приведенному выше, очень полезен. Может быть какой-то комбинатор доступен из библиотеки объективов для эзона, который также может быть использован здесь. – bheklilr