PureScript book Chapter 10 Exercise 6 (EncodeJson, DecodeJson Tree) - need some help

Hello,
I am new to PureScript and having a tough time to solve some JSON encoding issues from PureScript book - argonaut doesn’t like me yet.

This is what I’hve tried so far:

instance encodeJsonTree :: EncodeJson a => EncodeJson (Tree a) where                                                                                       
  encodeJson (Leaf a) = encodeJson a                                                                                                                       
  encodeJson (Branch tl tr) = encodeJson [tl, tr] -- same result as encodeJson [encodeJson tl, encodeJson tr]?                                                    
                                                                                                   
instance decodeJsonTree :: DecodeJson a => DecodeJson (Tree a) where                             
  decodeJson json = do                                                                           
    decoded <- decodeJson json                                                                   
    case decoded of                                                                              
      [tl,tr] -> Branch <$> decodeJson tl <*> decodeJson tr                                      
      a -> Right (Leaf a)                                                            
      _ -> Left $ TypeMismatch "Tree"

Error message for decodeJsonTree is a bit hard to understand:

[PureScript TypesDoNotUnify] [E] Could not match type

Array Json

with type

a0

while trying to match type Tree (Array Json)
with type Tree a0
while checking that expression DecodeJson$Dict { decodeJson: \json →

}
has type DecodeJson$Dict (Tree a0)
in value declaration decodeJsonTree

where a0 is a rigid type variable
bound at (line 0, column 0 - line 0, column 0)

After having tried to solve the exercise for longer time now - either resulting in an error result for spago test (this is the goal) or non-compilable code - I really appreciate some helping hint.

Exercise can be found here: The Foreign Function Interface - PureScript by Example → Exercise 6 (EncodeJson DecodeJson of Tree a).

(Note, I don’t want to use the generic solution from no-peeking section for learning purposes.)

Thanks!

Sorry my first answer was bad.
Guess I should try this myself instead of giving wrong answers :frowning:

You could try to get it into arrays of json values and then based on the array-size recursively decode into Branches or a Leaf.
What you did is very near to this and you don’t have to change much (hint: you got an array with two elements for Branch … what about Leaf?)

Another way might be to either add a tag (leaf or branch) which you should decode first and then continue with different decoders based on what the tag says.

Also there is an generic instance for arrays so encodeJson [tl, tr] will be an json-array with elements encodeJson tl and encodeJson tr.

And yes there is an instance for EncodeJson Json and yes it does nothing - so your commented question should be yes - it’s the same

1 Like

Thanks for the comment @CarstenKoenig !

Appreciated, this explains everything.


Now concerning compile error, let me try to question it this way:
I don’t know yet, why there is a typing problem in the first place.

Afaik
decodeJsonTree needs to return Either JsonDecodeError (Tree a).
Inside, decodeJson json returns an (untyped)

  • array
  • single value a with underlying DecodeJson instance
  • a mismatch error

from given JSON (which corresponds to the encoding part), and my impression was, that all fulfill above type requirement?

(Extra question: is there any magic, how decodeJson actually finds out, that an array is given, so it can be detected in subsequent pattern matching?).

So PureScript isn’t going to allow an “untyped” value at any point. So when you say decoded <- decodeJson json, what type would you suppose decoded is? The compiler is going to look at your pattern match, and assume decoded is some kind of array; it has to be, given that the first case is an array literal. And looking at the body of that case, it’s going to assume it must be an Array of Json (since you call decodeJson with the two elements). But then that second case is trying to treat it as a single a value, which is where the compiler error is coming from.

If you want to use decodeJson in your implementation, you’ll have to call it multiple times - once to attempt to decode it as an Array Json, and again to attempt to decode it as a single a. This is where the “magic” is that really threw me when I was first learning Argonaut. The implementation of decodeJson that gets executed (and the returned value) is actually different depending on the inferred return type. This makes sense when you look at some simple examples:

> decodeJson =<< parseJson "true" :: Either JsonDecodeError Int
(Left (TypeMismatch "Number"))
> decodeJson =<< parseJson "true" :: Either JsonDecodeError Boolean
(Right true)

Just by me changing the type annotation there, it returns a different value. A coworker of mine always likes to annotate each call to decodeJson so it feels a little less “magical,” but the PureScript compiler is happy to just infer all the types involved without any annotation.

1 Like

Ah, great explanation! Learned something about argonaut.

Also understanding my mistake now: I wanted to use pattern matching a bit too “freely” - neglecting the fact, that it only can compile with some static common type relationship/ADT between the cases. Might stem from my background with OOP subtyping and multiple dispatch.

If interested, the working solution now is:

-- copy paste [start]
data Tree a
  = Leaf a
  | Branch (Tree a) (Tree a)
-- copy paste [end]

instance encodeJsonTree :: EncodeJson a => EncodeJson (Tree a) where
  encodeJson (Leaf a) = encodeJson [a]
  encodeJson (Branch tl tr) = encodeJson [tl, tr]

instance decodeJsonTree :: DecodeJson a => DecodeJson (Tree a) where
  decodeJson json = do
    decoded <- decodeJson json
    case decoded of
      [tl,tr] -> Branch <$> decodeJson tl <*> decodeJson tr
      [a] -> Leaf <$> decodeJson a
      _ -> Left $ TypeMismatch "Tree"

derive instance eqTree :: Eq a => Eq (Tree a)
derive instance genericTree :: Generic (Tree a) _
instance showTree :: Show a => Show (Tree a) where
  show t = genericShow t

Again, thank you very much for the patience.

2 Likes