0

I am using purescript-agronauth library to manually encode and decode the following types to json and back. But the following does not work

data Attributes 
   = TextAlignment TextAlign
   | TextScale String
   | LineHeight String
instance encodeAttributes :: EncodeJson Attributes where
encodeJson r = 
    case r of
        (TextAlignment p) -> 
            "key" := (fromString "text-align")
            ~> "value" := p
        (TextScale p) -> 
            "key" := (fromString "font-size")
            ~> "value" := p
        (LineHeight p) -> 
            "key" := (fromString "line-height")
            ~> "value" := p
instance decodeElementAttributes :: DecodeJson ElementAttributes where
  decodeJson json = do
    obj <- decodeJson json
    key <- getField obj "key"
    value <- getField obj "value"
    case key of
        "text-align" -> Right $ TextAlignment value 
        "font-size" -> Right $ TextScale value
        "line-height" -> Right $ LineHeight value
        _ -> Left "Unkown element property"

data TextAlign
    = LeftAlign
    | RightAlign
    | CenterAlign
    | Justify
instance encodeTextAlign :: EncodeJson TextAlign where
    encodeJson r = 
        case r of
            LeftAlign -> fromString "left"
            RightAlign -> fromString "right"
            CenterAlign -> fromString "center"
            Justify -> fromString "justify"
instance decodeTextAlign :: DecodeJson TextAlign where
    decodeJson obj = do
       case toString obj of
         Just "left" -> Right LeftAlign
         Just "right" -> Right RightAlign
         Just "center" -> Right CenterAlign
         Just "justify" -> Right Justify
         Just _ -> Left "Unknown alignment"
         Nothing -> Left "Unknown alignment"

This gives the following error

  Could not match type

    TextAlign

  with type

    String


while checking that type t0
  is at least as general as type String
while checking that expression value
  has type String
in value declaration decodeElementAttributes

where t0 is an unknown type

Basically, I would like to know what would be the proper way to decode a Sum type like Attributes in this case

paluh
  • 2,171
  • 20
  • 14

1 Answers1

1

(..) But the following does not work

TLDR; This should work:

instance decodeElementAttributes :: DecodeJson Attributes where
  decodeJson json = do
    obj <- decodeJson json
    key <- getField obj "key"
    case key of
      "text-align" -> TextAlignment <$> getField obj "value"
      "font-size" -> TextScale <$> getField obj "value"
      "line-height" -> LineHeight <$> getField obj "value"
      _ -> Left "Unkown element property"

Let's jump into compiler shoes for a moment and try to infer the value type. In the monadic block in decodeJson there is a call to getField:

value <- getField obj "value"

getField is polymorphic on its return type:

getField :: forall a. DecodeJson a => Object Json -> String -> Either String a

So from this call alone we are not able to guess the type of the value. We need some more information / context.

But luckily few lines below we can find a usage of value which gives us some solution:

"text-align" -> Right $ TextAlignment value

So for sure our value has to be typed as TextAlign because TextAlignment constructor expects such a parameter.

But wait... just line underneath there is another usage of value:

"font-size" -> Right $ TextScale value

and here we have a problem because this tells us that value has type String and... TextAlign at the same time... We have no other choice than tell the World about our discovery:

Could not match type

 TextAlign

with type

  String

Basically, I would like to know what would be the proper way to decode a Sum type like Attributes in this case

  • Your approach is OK to me. It gives you full control over coding / decoding process. It can be error prone though...

  • You can give a try and use fully generic solution like purescript-argounaut-generic.

  • You can also try different generic approach and use purescript-simple-json. I wasn't able to find example for generic sum handling - here is only enum like type encoded / decoded: https://www.reddit.com/r/purescript/comments/7b5y7q/some_extra_examples_of_simplejson_usage/. You can always ask Justin Woo for suggestions - he is really responsive author :-)

  • I haven't used purescript-codec-argonaut yet but it should help you minimize some duplication related to coding and decoding definitions. With this approach you are still responsible for defining everything by hand I think.

  • Here is interesting post, which I think is mostly relevant if you do not have PureScript on both ends of the wire (consumer and producer) by @garyb about downsides of generic codecs: http://code.slipthrough.net/2018/03/13/thoughts-on-typeclass-codecs/

Do you have PureScript on both ends of the wire?

paluh
  • 2,171
  • 20
  • 14
  • Thanks very much for your answer. The compiler errors makes a lot of sense now. I had initially opted for SimpleJSON. but I did not understand its working much, it seemed like some kind of voodoo magic. Further I had some experience working with encoding/decoding JSON in elm so argonaut was a little more easy to understand. Could you explain why encoding/decoding like this can be error prone? – Akshay Kumar Nov 12 '18 at 14:26
  • This project is still in the early stages. I haven't done anything on the backend yet but yes I am considering sticking to Purescript. – Akshay Kumar Nov 12 '18 at 14:26
  • @AkshayKumar If you have PureScript on both ends I would go with `purescript-argonaut-generic` because you don't have to worry about some custom encoding of specific fields etc. I've written that manual encoding/decoding can be error prone because you have to keep both stages in sync... manually ;-) I'm working in PS on both ends too: frontend and backend so if you have any more questions I'm more than happy to answer. You can find me easily on #purescript / #purescript-beginners on slack or on purescript discourse too ;-) – paluh Nov 12 '18 at 17:09