Aeson and error handling lenses

I am very new to lens magic, so I have problems with this.

Referring to: https://www.fpcomplete.com/user/tel/lens-aeson-traversals-prisms

A JSON object can be traversed as follows:

val ^? nth 0 . key "someObject" . key "version" . nth 2 

for a JSON object that resembles:

 "[{\"someObject\": {\"version\": [1, 0, 3]}}]" 

Maybe Maybe monad is used everywhere, so if any "accessor" fails, I get a Nothing .

I would also like to propagate the failure so that I know what failed to get.

The only way I can do this is to pass an array of accessories, apply them sequentially and return an error at any time that failed. Something like that:

 import Data.Aeson import Data.Text import Data.Vector ((!?)) import qualified Data.HashMap.Strict as HM data MyAccessor = Nth Int | Key Text withFailure :: Value -> [MyAccessor] -> Either String Value withFailure val [] = Right val withFailure val (x:xs) = case x of Nth i -> case val of (Array val') -> case (val' !? i) of Just e -> withFailure e xs _ -> Left $ "Could not get index " ++ (show i) _ -> Left $ "Expected JSON array for index " ++ (show i) Key k -> case val of (Object val') -> case (HM.lookup k val') of Just e -> withFailure e xs _ -> Left $ "Could not get key " ++ (unpack k) _ -> Left $ "Expected JSON object for key " ++ (unpack k) 

So with this:

 -- val = [[1,0,3], {"name" : "value"}] > withFailure val [Nth 1, Key "name", Key "hello"] Left "Expected JSON object for key hello" > withFailure val [Nth 1, Key "name"] Right (String "value") 

Is there a more elegant way to do this? Creating a monastic monad to use the lens, which leads to what withFailure ?

+6
source share
1 answer

Another possibility is to use monadic folds from Control.Lens.Action .

Monadic folds allow you to transfer your fold with effective actions so that these actions are interconnected with the process of โ€œstudyingโ€ the data structure.

Note that this is different from mapMOf . Monadic folds allow you to do something like creating parts of the structure that are explored with the word "on the fly", for example, by loading them from disk or asking the user for input.

Ordinary folds are directly used as monadic folds. You just need to run them with specialized operators such as (^!!) and (^!?) .

To introduce an effect into a monadic fold, use the act function.

We can create a monadic fold working in the Writer monad and insert bend actions that "log". Something like that:

 {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleContexts #-} import Control.Monad import Control.Monad.Writer import Control.Lens import Data.Monoid import Data.Aeson import Data.Aeson.Lens msg :: String -> IndexPreservingAction (Writer (Last String)) aa msg str = act $ \a -> tell (Last . Just $ str) >> return a main :: IO () main = do let str = "[{\"someObject\": {\"version\": [1, 0, 3]}}]" val = maybe (error "decode err") id . decode $ str :: Value monfol = nth 0 . msg "#1" . key "someObject" . msg "#2" . key "version" . msg "#3" . nth 2 (mresult,message) = runWriter $ val ^!? monfol putStrLn $ case mresult of Just result -> show result Nothing -> maybe "no messages" id . getLast $ message 

If you change the "version" key in JSON to collapse the failure, the error message will be "# 2".

It would be nice to use some kind of error monad, like Either instead of Writer , to pinpoint the location of the failure, rather than the last "breakpoint". But I'm not sure if this is possible, because the fold already represents an error, returning Nothing .

The Control.Lens.Reified module has a ReifiedMonadicFold newtype that offers some useful instances for monadic folds. ReifiedMonadicFold behave a bit like the arrows of the Claysley monad, which is an instance of MonadPlus .

+4
source

Source: https://habr.com/ru/post/971828/


All Articles