2014-07-06 13 views
6

Jestem nowy w magii soczewek, więc mam z tym problem.Aeson i soczewki z obsługą błędów

W odniesieniu do: https://www.fpcomplete.com/user/tel/lens-aeson-traversals-prisms

obiekt JSON może być wykonywany w następujący sposób:

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

dla obiektu JSON, który przypomina:

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

Może monada jest używany w całym, więc jeśli jakikolwiek "accessor" nie powiedzie się, otrzymuję Nothing.

Chciałbym również propagować awarię, aby wiedzieć, co nie udało się uzyskać.

Jedyny sposób, w jaki mogę to zrobić, to przekazanie tablicy akcesorów, zastosowanie ich kolejno i zwrócenie błędu w dowolnym momencie, który się nie powiódł. Coś takiego:

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) 

Więc z tego:

-- 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") 

Czy jest bardziej elegancki sposób to zrobić? Tworzenie monady na obiektyw do użycia, które skutkuje tym, czym jest withFailure?

Odpowiedz

4

Inną możliwością jest użycie monadycznych fałd z Control.Lens.Action.

Monadyczne fałdy pozwalają na poderwanie fałdka efektownymi działaniami, dzięki czemu działania te zostają wplątane w proces "eksplorowania" struktury danych.

Zauważ, że różni się to od czegoś takiego jak mapMOf. Monadyczne fałdy pozwalają na takie rzeczy jak tworzenie części struktury badanej przez fałdowanie "w locie", na przykład przez ładowanie ich z dysku lub proszenie użytkownika o dane wejściowe.

Zwykłe fałdy są bezpośrednio używane jako monadyczne fałdy. Trzeba je uruchomić u wyspecjalizowanych operatorów, takich jak (^!!) i (^!?).

Aby wprowadzić efekt do monadycznej zakładki, użyj funkcji act.

Możemy stworzyć monadyczną fałdę działającą w monadzie Writer i wstawić akcje do zakładki, która "loguje" postęp. Coś takiego:

{-# 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)) a a 
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 

przypadku zmiany klucza „Wersja” w JSON aby klapka nie, komunikat o błędzie będzie „# 2”.

Byłoby miło użyć jakiegoś monada błędu, jak Either zamiast Writer, aby móc dokładnie wskazać miejsce awarii, zamiast ostatniego "punktu kontrolnego". Ale nie jestem pewien, czy jest to możliwe, ponieważ fałd już reprezentuje niepowodzenie, zwracając Nothing.

Moduł Control.Lens.Reified ma nowy typ ReifiedMonadicFold, który oferuje kilka użytecznych instancji dla monadycznych zagięć. ReifiedMonadicFold s zachowywać się trochę jak strzały Kleisli monady, która jest instancją MonadPlus.