6

我对镜头的魔力还很陌生,所以我遇到了一些麻烦。

参考:https ://www.fpcomplete.com/user/tel/lens-aeson-traversals-prisms

可以通过以下方式遍历 JSON 对象:

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

对于类似于以下内容的 JSON 对象:

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

Maybe monad 一直在使用,所以如果任何“访问器”失败,我会得到一个Nothing.

我也想传播失败,以便我知道哪个访问器失败了。

我能想到的唯一方法是传递一个访问器数组,按顺序应用它们,并在任何失败的地方返回一个错误。像这样的东西:

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)

所以有了这个:

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

有没有更优雅的方式来做到这一点?为镜头制作一个 Either-ish monad,结果是什么withFailure

4

1 回答 1

4

另一种可能性是使用Control.Lens.Action.

一元折叠让您可以在折叠中添加有效的操作,以便这些操作与“探索”数据结构的过程相互交织。

请注意,这与类似mapMOf. Monadic 折叠允许您执行诸如“动态”创建折叠正在探索的结构的某些部分之类的操作,例如通过从磁盘加载它们,或要求用户输入。

正常折叠可直接用作一元折叠。你只需要使用专门的操作符来运行它们,比如(^!!)and (^!?)

要将效果引入一元折叠,请使用该act函数。

我们可以在Writermonad 中创建一个单子折叠,并在折叠中插入“记录”进度的操作。像这样的东西:

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

如果您更改 JSON 中的“version”键以使折叠失败,则错误消息将为“#2”。

最好使用某种错误单子Either代替Writer,以便能够准确定位失败的位置,而不是最后一个“检查点”。但我不确定这是否可能,因为 fold 已经通过返回表示失败Nothing

模块Control.Lens.Reified具有ReifiedMonadicFold为单子折叠提供一些有用实例的新类型。ReifiedMonadicFolds 的行为有点像 monad 的 Kleisli 箭头,它是MonadPlus.

于 2014-07-06T18:05:31.613 回答