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" ),只有当堆栈中的某个人真的厌倦了对返回值做某事时才会发生实际调用。
这是默认行为,这就是 json
和 decode
的工作方式。但是有一种方法可以 "cheat" 懒惰并告诉编译器执行代码并立即评估值。这就是 json'
和 decode'
所做的。
那里的权衡是显而易见的:decode
节省了计算时间,以防您实际上从未对该值做任何事情,而 decode'
节省了 "remember" 调用信息的必要性( "thunk") 以执行所有内容为代价。
这两者之间的区别很微妙。有的区别,但有点复杂。我们可以先看看类型。
Value
类型
值得注意的是,aeson 提供的 Value
类型在很长一段时间内都是严格的(具体来说,从 0.4.0.0 版本开始)。这意味着 Value
的构造函数与其内部表示之间不能有任何 thunk。这立即意味着 Bool
(当然还有 Null
) 必须 一旦 Value
被评估为 WHNF。
接下来,让我们考虑 String
和 Number
。 String
构造函数包含一个 strict Text
类型的值,因此那里也不能有任何惰性。类似地,Number
构造函数包含一个 Scientific
值,它在内部由两个严格值表示。一旦 Value
被评估为 WHNF,String
和 Number
都必须 也 被完全评估。
我们现在可以将注意力转向 Object
和 Array
,这是 JSON 提供的唯一重要数据类型。这些更有趣。 Object
s 在 aeson 中由 lazy HashMap
表示。惰性 HashMap
s 只评估他们的键到 WHNF,而不是他们的值,所以这些值很可能保持未评估的 thunks。类似地,Array
s 是 Vector
s,它们的值也不严格。这两种 Value
都可以包含 thunk。
考虑到这一点,我们知道,一旦我们有了 Value
,只有 个地方 decode
和 decode'
可能不同之处在于对象和数组的生成。
观察差异
接下来我们可以尝试的是实际评估 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'
现在我们有两个绑定,parsed
和 parsed'
,其中一个用 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 解码环形。无论哪种情况,您都应该进行基准测试。在 decode
和 decode'
之间进行选择是一项非常具体的手动优化,如果没有基准测试,我不太相信这两者是否会真正改善我的程序的运行时特性。
功能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" ),只有当堆栈中的某个人真的厌倦了对返回值做某事时才会发生实际调用。
这是默认行为,这就是 json
和 decode
的工作方式。但是有一种方法可以 "cheat" 懒惰并告诉编译器执行代码并立即评估值。这就是 json'
和 decode'
所做的。
那里的权衡是显而易见的:decode
节省了计算时间,以防您实际上从未对该值做任何事情,而 decode'
节省了 "remember" 调用信息的必要性( "thunk") 以执行所有内容为代价。
这两者之间的区别很微妙。有的区别,但有点复杂。我们可以先看看类型。
Value
类型
值得注意的是,aeson 提供的 Value
类型在很长一段时间内都是严格的(具体来说,从 0.4.0.0 版本开始)。这意味着 Value
的构造函数与其内部表示之间不能有任何 thunk。这立即意味着 Bool
(当然还有 Null
) 必须 一旦 Value
被评估为 WHNF。
接下来,让我们考虑 String
和 Number
。 String
构造函数包含一个 strict Text
类型的值,因此那里也不能有任何惰性。类似地,Number
构造函数包含一个 Scientific
值,它在内部由两个严格值表示。一旦 Value
被评估为 WHNF,String
和 Number
都必须 也 被完全评估。
我们现在可以将注意力转向 Object
和 Array
,这是 JSON 提供的唯一重要数据类型。这些更有趣。 Object
s 在 aeson 中由 lazy HashMap
表示。惰性 HashMap
s 只评估他们的键到 WHNF,而不是他们的值,所以这些值很可能保持未评估的 thunks。类似地,Array
s 是 Vector
s,它们的值也不严格。这两种 Value
都可以包含 thunk。
考虑到这一点,我们知道,一旦我们有了 Value
,只有 个地方 decode
和 decode'
可能不同之处在于对象和数组的生成。
观察差异
接下来我们可以尝试的是实际评估 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'
现在我们有两个绑定,parsed
和 parsed'
,其中一个用 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 解码环形。无论哪种情况,您都应该进行基准测试。在 decode
和 decode'
之间进行选择是一项非常具体的手动优化,如果没有基准测试,我不太相信这两者是否会真正改善我的程序的运行时特性。