json - What is difference between decode and decode' functions from aeson package? -
functions decode
, decode'
aeson
package identical. have subtle difference described in documentation (posting interesting part of docs here):
-- function parses immediately, defers conversion. see -- 'json' details. decode :: (fromjson a) => l.bytestring -> maybe decode = decodewith jsoneof fromjson -- function parses , performs conversion immediately. see -- 'json'' details. decode' :: (fromjson a) => l.bytestring -> maybe decode' = decodewith jsoneof' fromjson
i tried read description of json
, json'
functions still don't understand 1 , when should use because documentation not clear enough. can describe more precisely difference between 2 functions , provide example behavior explanation if possible?
update:
there decodestrict
, decodestrict'
functions. i'm not asking difference between decode'
, decodestrict
example way interesting question well. what's lazy , what's strict here in these functions not obvious @ all.
the difference between these 2 subtle. there is difference, it’s little complicated. can start taking @ types.
the value
type
it’s important note value
type aeson provides has been strict long time (specifically, since version 0.4.0.0). means there cannot thunks between constructor of value
, internal representation. means bool
(and, of course, null
) must evaluated once value
evaluated whnf.
next, let’s consider string
, number
. string
constructor contains value of type strict text
, there can’t laziness there, either. similarly, number
constructor contains scientific
value, internally represented 2 strict values. both string
, number
must also evaluated once value
evaluated whnf.
we can turn our attention object
, array
, nontrivial datatypes json provides. these more interesting. object
s represented in aeson lazy hashmap
. lazy hashmap
s evaluate keys whnf, not values, values remain unevaluated thunks. similarly, array
s vector
s, not strict in values, either. both of these sorts of value
s can contain thunks.
with in mind, know that, once have value
, only places decode
, decode'
may differ in production of objects , arrays.
observational differences
the next thing can try evaluate things in ghci , see happens. we’ll start bunch of imports , definitions:
: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 m import qualified data.vector v :{ forcespine :: [a] -> io () forcespine = evaluate . foldl' const () :}
next, let’s parse 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'
now have 2 bindings, parsed
, parsed'
, 1 of parsed decode
, other decode'
. forced whnf can @ least see are, can use :sprint
command in ghci see how of each value evaluated:
ghci> :sprint parsed parsed = _ ghci> :sprint parsed' parsed' = (object (unordered-containers-0.2.8.0:data.hashmap.base.leaf 15939318180211476069 (data.text.internal.text _ 0 5) (array (data.vector.vector 0 2 _))))
would @ that! version parsed decode
still unevaluated, 1 parsed decode'
has data. leads our first meaningful difference between two: decode'
forces immediate result whnf, decode
defers until needed.
let’s inside these values see if can’t find more differences. happens once evaluate outer objects?
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 _)))
this pretty obvious. explicitly forced both of objects, both evaluated hash maps. real question whether or not elements evaluated.
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 _)))]
another difference! array decoded decode
, values not forced, ones decoded decode'
are. can see, means decode
doesn’t perform conversion haskell values until needed, documentation means when says “defers conversion”.
impact
clearly, these 2 functions slightly different, , clearly, decode'
stricter decode
. what’s meaningful difference, though? when prefer 1 on other?
well, it’s worth mentioning decode
never more work decode'
, decode
right default. of course, decode'
never more work decode
, either, since entire json document needs parsed before value can produced. significant difference decode
avoids allocating value
s if small part of json document used.
of course, laziness not free, either. being lazy means adding thunks, can cost space , time. if of thunks going evaluated, anyway, decode
wasting memory , runtime adding useless indirection.
in sense, situations when might want use decode'
situations in whole value
structure going forced, anyway, dependent on fromjson
instance you’re using. in general, wouldn’t worry picking between them unless performance matters and you’re decoding lot of json or doing json decoding in tight loop. in either case, should benchmark. choosing between decode
, decode'
specific manual optimization, , not feel confident either improve runtime characteristics of program without benchmarks.
Comments
Post a Comment