首页 > 解决方案 > 在 purescript 中解码联合类型

问题描述

我正在使用 purescript-agronauth 库手动将以下类型编码和解码为 json 并返回。但以下不起作用

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"

这给出了以下错误

  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

基本上,我想知道在这种情况下解码像 Attributes 这样的 Sum 类型的正确方法是什么

标签: purescript

解决方案


(..) 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?


推荐阅读