aeson包的解码和解码功能有什么区别?

She*_*rsh 14 json haskell decoding lazy-evaluation aeson

功能decodedecode'aeson封装几乎是相同的.但是它们在文档中描述了微妙的差异(这里仅发布有趣的文档部分):

-- This function parses immediately, but defers conversion.  See
-- 'json' for details.
decode :: (FromJSON a) => L.ByteString -> Maybe a
decode = decodeWith jsonEOF fromJSON

-- This function parses and performs conversion immediately.  See
-- 'json'' for details.
decode' :: (FromJSON a) => L.ByteString -> Maybe a
decode' = decodeWith jsonEOF' fromJSON
Run Code Online (Sandbox Code Playgroud)

我试着阅读描述jsonjson'功能,但仍然不明白哪一个以及何时应该使用,因为文档不够清晰.任何人都可以更准确地描述两个函数之间的区别,并提供一些行为解释的例子吗?

更新:

也有decodeStrictdecodeStrict'功能.我不要求什么区别decode'decodeStrict例如它的方式是一个有趣的问题也是如此.但是在所有这些功能中,什么是懒惰的以及严格的内容并不明显.

Ale*_*ing 14

这两者之间的区别是微妙的.这里一个区别,但它是一个有点复杂.我们可以先看一下类型.

Value类型

重要的是要注意,Valueaeson提供的类型在很长一段时间内都是严格的(特别是从版本0.4.0.0开始).这意味着构造函数Value和其内部表示之间不会有任何thunk .这立即意味着一旦a 评估为WHNF Bool,必须完全评估(当然Null).Value

接下来,让我们考虑StringNumber.该String构造包含类型的值严格 Text,所以不能有任何懒惰那边.类似地,Number构造函数包含一个Scientific值,该值在内部由两个严格值表示.双方StringNumber必须一次彻底评估Value进行评估,以WHNF.

现在,我们可以把我们的关注ObjectArray,即JSON提供了唯一的非平凡的数据类型.这些更有趣.Objects由懒惰 代表在aeson中HashMap.懒惰HashMap只评估他们对WHNF的关键,而不是他们的价值,所以价值很可能仍然是未评估的价值.类似地,Arrays是Vectors,它们的值也不严格.这两种Values都可以包含thunk.

考虑到这一点,我们知道,一旦我们拥有了一个Value,那么唯一decodedecode'可能不同的地方就是对象和数组的生成.

观察差异

接下来我们可以尝试实际评估GHCi中的一些事情,看看会发生什么.我们将从一堆导入和定义开始:

:seti -XOverloadedStrings

import Control.Exception
import Control.Monad
import Data.Aeson
import Data.ByteString.Lazy (ByteString)
import Data.List (foldl')
import qualified Data.HashMap.Lazy as M
import qualified Data.Vector as V

:{
forceSpine :: [a] -> IO ()
forceSpine = evaluate . foldl' const ()
:}
Run Code Online (Sandbox Code Playgroud)

接下来,让我们实际解析一些JSON:

let jsonDocument = "{ \"value\": [1, { \"value\": [2, 3] }] }" :: ByteString

let !parsed = decode jsonDocument :: Maybe Value
let !parsed' = decode' jsonDocument :: Maybe Value
force parsed
force parsed'
Run Code Online (Sandbox Code Playgroud)

现在我们有两个绑定,parsed并且parsed',其中的一个进行解析decode和其他与decode'.他们被迫使用WHNF,所以我们至少可以看到它们是什么,但是我们可以使用:sprintGHCi中的命令来查看实际评估的每个值的大小:

ghci> :sprint parsed
parsed = Just _
ghci> :sprint parsed'
parsed' = Just
            (Object
               (unordered-containers-0.2.8.0:Data.HashMap.Base.Leaf
                  15939318180211476069 (Data.Text.Internal.Text _ 0 5)
                  (Array (Data.Vector.Vector 0 2 _))))
Run Code Online (Sandbox Code Playgroud)

你看看那个!解析的版本decode仍未评估,但解析的版本decode'有一些数据.这导致我们在两者之间的第一个有意义的区别:decode'将其直接结果强制给WHNF,但decode在需要之前将其推迟.

让我们看看这些值,看看我们是否找不到更多的差异.一旦我们评估这些外部物体会发生什么?

let (Just outerObjValue) = parsed
let (Just outerObjValue') = parsed'
force outerObjValue
force outerObjValue'

ghci> :sprint outerObjValue
outerObjValue = Object
                  (unordered-containers-0.2.8.0:Data.HashMap.Base.Leaf
                     15939318180211476069 (Data.Text.Internal.Text _ 0 5)
                     (Array (Data.Vector.Vector 0 2 _)))

ghci> :sprint outerObjValue'
outerObjValue' = Object
                   (unordered-containers-0.2.8.0:Data.HashMap.Base.Leaf
                      15939318180211476069 (Data.Text.Internal.Text _ 0 5)
                      (Array (Data.Vector.Vector 0 2 _)))
Run Code Online (Sandbox Code Playgroud)

这很明显.我们明确强制了两个对象,因此它们现在都被评估为哈希映射.真正的问题是他们的元素是否被评估.

let (Array outerArr) = outerObj M.! "value"
let (Array outerArr') = outerObj' M.! "value"
let outerArrLst = V.toList outerArr
let outerArrLst' = V.toList outerArr'

forceSpine outerArrLst
forceSpine outerArrLst'

ghci> :sprint outerArrLst
outerArrLst = [_,_]

ghci> :sprint outerArrLst'
outerArrLst' = [Number (Data.Scientific.Scientific 1 0),
                Object
                  (unordered-containers-0.2.8.0:Data.HashMap.Base.Leaf
                     15939318180211476069 (Data.Text.Internal.Text _ 0 5)
                     (Array (Data.Vector.Vector 0 2 _)))]
Run Code Online (Sandbox Code Playgroud)

另一个区别!对于使用解码的数组decode,值不是强制的,而是用于解码的值decode'.正如您所看到的,这意味着decode在实际需要之前,实际上并不会执行到Haskell值的转换,这就是文档在说"延迟转换"时的意思.

碰撞

显然,这两个功能略有不同,而且显然decode'比它更严格decode.但是有什么意义呢?你什么时候比另一个更喜欢?

嗯,值得一提的是,decode从来没有比这更多的工作decode',所以decode可能是正确的默认.当然,decode'永远不会做更多的工作decode,因为在生成任何值之前需要解析整个JSON文档.唯一显着的区别是,如果实际使用的只是JSON文档的一小部分,则decode避免分配Values.

当然,懒惰也不是免费的.懒惰意味着添加thunk,这可能会花费空间和时间.如果要评估所有的thunk,那么decode就是浪费内存和运行时添加无用的间接.

从这个意义上讲,您可能想要使用decode'的情况是整个Value结构将被强制的情况,这可能取决于FromJSON您正在使用的实例.一般情况下,我不担心在它们之间进行选择,除非性能真的很重要并且您正在解码大量JSON或在紧密循环中进行JSON解码.无论哪种情况,都应该进行基准测试 在两者之间进行选择decode并且decode'是非常具体的手动优化,我不会非常有信心在没有基准测试的情况下实际上可以改善程序的运行时特性.