解码和解码之间的区别是什么?来自aeson包的函数?

时间:2017-07-28 13:11:52

标签: json haskell lazy-evaluation decoding aeson

来自decode包的函数decode'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

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

更新

还有decodeStrictdecodeStrict'个功能。我并不是在问decode'decodeStrict之间有什么区别,例如顺便说一句,这也是一个有趣的问题。但是,在所有这些功能中,什么是懒惰和严格的内容并不明显。

2 个答案:

答案 0 :(得分:14)

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

Value类型

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

接下来,让我们考虑StringNumberString构造函数包含严格 Text类型的值,因此也不存在任何懒惰。类似地,Number构造函数包含Scientific值,该值由两个严格值在内部表示。一旦String被评估为WHNF,NumberValue必须完全评估。

现在我们可以将注意力转向ObjectArray,这是JSON提供的唯一非常重要的数据类型。这些更有趣。 Object lazy HashMap在aeson中表示。懒惰HashMap只评估他们对WHNF的关键,而不是他们的价值,所以价值很可能仍然是未评估的价值。同样地,Array s是Vector s,它们的值也不严格。这两种Value都可以包含thunk。

考虑到这一点,我们知道,一旦我们有Valuedecodedecode'可能不同的位置就会出现在制作中对象和数组。

观察差异

接下来我们可以尝试实际评估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 ()
:}

接下来,让我们实际解析一些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'

现在我们有两个绑定parsedparsed',其中一个用decode解析,另一个用decode'解析。他们被迫使用WHNF,所以我们至少可以看到它们是什么,但我们可以在GHCi中使用:sprint命令来查看实际评估的每个值的多少:

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 _))))
你会看到那个!使用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 _)))

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

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 _)))]

另一个区别!对于使用decode解码的数组,不会强制使用值,而是使用decode'解码的值。正如您所看到的,这意味着decode实际上并不实际执行到Haskell值的转换,直到实际需要它们为止,这就是文档在说“延迟转换”时的含义。

影响

显然,这两个函数略有不同,显然decode'decode更严格。但是有什么意义呢?你什么时候比另一个更喜欢?

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

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

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

答案 1 :(得分:1)

Haskell是一种懒惰的语言。当你调用一个函数时,它实际上并没有正确执行,而是关于调用的信息是#34;记住"并且返回堆栈(这个记住的呼叫信息在文档中被称为" thunk"并且实际呼叫仅在堆栈中的某人实际上厌倦了对返回值执行某些操作时发生。

这是默认行为,这就是jsondecode的工作方式。但有一种方法可以欺骗"懒惰并告诉编译器执行代码并在那里评估值。这就是json'decode'所做的事情。

显而易见的是:decode可以节省计算时间,以防您实际上从未对该值进行任何操作,而decode'可以节省必要的时间来记住"呼叫信息(" thunk"),代价是执行所有操作。