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

What is difference between decode and decode' functions from aeson package?

功能decode and decode' from 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

我试图阅读 json and json' 函数的描述,但仍然不明白我应该使用哪一个以及何时使用,因为文档不够清晰。谁能更准确地描述两个函数之间的区别,并在可能的情况下提供一些行为解释示例?

更新:

还有decodeStrict and decodeStrict'个函数。例如,我不是在问 decode'decodeStrict 之间有什么区别,这也是一个有趣的问题。但是在所有这些函数中什么是惰性的什么是严格的并不明显。

Haskell 是一种懒惰的语言。当你调用一个函数时,它实际上并没有立即执行,而是关于调用的信息是 "remembered" 并返回堆栈(这个记住的调用信息在文档中被称为 "thunk" ),只有当堆栈中的某个人真的厌倦了对返回值做某事时才会发生实际调用。

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

那里的权衡是显而易见的:decode 节省了计算时间,以防您实际上从未对该值做任何事情,而 decode' 节省了 "remember" 调用信息的必要性( "thunk") 以执行所有内容为代价。

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

Value类型

值得注意的是,aeson 提供的 Value 类型在很长一段时间内都是严格的(具体来说,从 0.4.0.0 版本开始)。这意味着 Value 的构造函数与其内部表示之间不能有任何 thunk。这立即意味着 Bool(当然还有 Null 必须 一旦 Value 被评估为 WHNF。

接下来,让我们考虑 StringNumberString 构造函数包含一个 strict Text 类型的值,因此那里也不能有任何惰性。类似地,Number 构造函数包含一个 Scientific 值,它在内部由两个严格值表示。一旦 Value 被评估为 WHNF,StringNumber 都必须 被完全评估。

我们现在可以将注意力转向 ObjectArray,这是 JSON 提供的唯一重要数据类型。这些更有趣。 Objects 在 aeson 中由 lazy HashMap 表示。惰性 HashMaps 只评估他们的键到 WHNF,而不是他们的值,所以这些值很可能保持未评估的 thunks。类似地,Arrays 是 Vectors,它们的值也不严格。这两种 Value 都可以包含 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 ()
:}

接下来,让我们实际解析一些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 避免分配 Value 如果实际上只使用 JSON 文档的一小部分。

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

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