Ответ 1
Разница между этими двумя является тонкой. Есть разница, но это немного сложно. Мы можем начать с изучения типов.
Тип Value
Важно отметить, что тип Value
, который предоставляет aeson, был строгим в течение очень долгого времени (в частности, начиная с версии 0.4. 0.0). Это означает, что между конструктором Value
и его внутренним представлением не должно быть никаких трюков. Это немедленно означает, что Bool
(и, конечно, Null
) должен быть полностью оценен, как только a Value
оценивается WHNF.
Затем рассмотрим String
и Number
. Конструктор String
содержит значение типа строгое Text
, поэтому там может быть любая лень. Аналогично, конструктор Number
содержит значение Scientific
, которое внутренне представлено двумя строгими значениями. И String
, и Number
также должны быть полностью оценены после того, как a Value
оценивается как WHNF.
Теперь мы можем обратить внимание на Object
и Array
, единственные нетривиальные типы данных, которые предоставляет JSON. Это более интересно. Object
представлены в aeson ленивым HashMap
. Lazy HashMap
оценивают только свои ключи от WHNF, а не их значения, поэтому значения могут очень сильно отличаться от неоцененных thunks. Аналогично, Array
являются Vector
s, которые также не являются строгими по своим значениям. Оба эти типа Value
могут содержать thunks.
Учитывая это, мы знаем, что, когда мы имеем 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, чтобы мы могли хотя бы увидеть, что они есть, но мы можем использовать команду :sprint
в GHCi, чтобы узнать, сколько из каждого значения действительно оценивается:
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.
Конечно, лень также не является бесплатным. Быть ленивым означает добавление гроз, которое может стоить пространства и времени. Если все тонкости будут оцениваться, так или иначе, то decode
просто растрачивает память и время выполнения, добавляя бесполезную косвенность.
В этом смысле ситуации, когда вы, возможно, захотите использовать decode'
, - это ситуации, в которых вся структура Value
будет вынуждена, во всяком случае, которая, вероятно, зависит от того, какой экземпляр вы используете FromJSON
. В общем, я бы не стал беспокоиться о выборе между ними, если производительность не имеет особого значения, и вы расшифровываете много JSON или делаете JSON-декодирование в плотном цикле. В любом случае, вы должны ориентироваться. Выбор между decode
и decode'
- это очень конкретная ручная оптимизация, и я не был бы уверен в том, что либо на самом деле улучшит характеристики во время выполнения моей программы без тестов.