Why is pattern matching on Argonaut Json "disabled"?

256 views
Skip to first unread message

Rahul Goma Phulore

unread,
Aug 15, 2014, 3:41:40 PM8/15/14
to argona...@googlegroups.com
Hi all.

I initially asked this question on Twitter [1], and was going to gist the details, but figured it would be better to post to mailing list instead.

We have a large-ish codebase that has to deal with a lot of complex JSON structures. We are currently using Play JSON, but due to some of its shortcomings, we are considering moving to a different JSON library. This is the first time I am trying out Argonaut, so please forgive my ignorance of it.

I am surprised by some of the key design decisions that Argonaut has made – #1 hiding data constructor types, and #2 discouraging pattern matching outright. I do not know the reason for #1, and I have posted a question about it on StackOverflow. [2] For #2, I think I get the point about combinators being more functional and better solution (here is the only concrete material I have read that touches on the subject - [3]), but I am not sure if combinators subsume all kind of things that one can do with pattern matching. (Unless you have something as full-fledged as Data.Pattern. [4])

But then, I humbly concede that I don't have as much depth in the subject as you all, and would love to have my opinions changed/refined.

Here are some cases I have handpicked from our codebase (sscce-fied [5]), for which the alternatives are not clear to me:

1. Ad hoc parsing into a custom complex type. The function is recursive, and we are also using regex extractors here.

def parseThrak(json: JsValue): Seq[Thrak] = json match {
  case JsString(Regex1(x)) => Seq(SimpleText(x))
  case JsString(Regex2(x)) => Seq(ComplexText(x))
  case o @ JsObject(_) if o.containsKeys("k1") => Seq(Lov(o))
  case o @ JsObject(_) if o.containsKeys("k2") => Seq(Uom(o))
  case JsArray(xs) => xs.flatMap(parseThrak)
  case x => sys error s"oops: $x"
}

2. Recursive processing over arrays and objects.

def processJson(json: JsValue): JsValue = json match {
   case JsString(str)      => JsString(processString(str))
   case JsArray(values) => JsArray(values.map(processJson))
   case JsObject(pairs)  => JsObject(pairs.map { case (k, v) => (k -> processJson(v))})
   case json                  => json
}

3. Effectful functions that deal with JSONs, and which are recursive.

def processJsonFO(json: JsValue): Future[Option[JsValue]] = json match {
  // ...
}

4. Throw away noisy stuff; just keep what is interesting.

jsValues collect {
  case JsString(x) => x
}

etc.

Could you please suggest how these things could be done with the combinators provided in Argonaut?

Cheers,
Rahul.

Mark Hibberd

unread,
Aug 15, 2014, 5:30:42 PM8/15/14
to Rahul Goma Phulore, argona...@googlegroups.com


I not at my machine, so I will have to defer longer answers until later. But some detail inline.

On 16/08/2014 6:18 am, "Rahul Goma Phulore" <rahul.ph...@gmail.com> wrote:
>
> Hi all.
>
> I initially asked this question on Twitter [1], and was going to gist the details, but figured it would be better to post to mailing list instead.
>
> We have a large-ish codebase that has to deal with a lot of complex JSON structures. We are currently using Play JSON, but due to some of its shortcomings, we are considering moving to a different JSON library. This is the first time I am trying out Argonaut, so please forgive my ignorance of it.
>
> I am surprised by some of the key design decisions that Argonaut has made – #1 hiding data constructor types,

Making the data type abstract provides a couple of essential things:

- allows invariants to be maintained (I.e. number can't be NaN or Inf)

- allows binary and source compatible changes to internal representations. This has been leveraged for non-trivial performance gains multiple times.

It also has some nice consequences within the constraints of Scala:
- hiding any subclass has nice properties with respect to invariant data types
- prevents users from making mistake of passing around constructors rather than their context. This is desirable, as if you ever learn something, such as the specific constructor, you never want to "lose" that knowledge in the type, which is what happens when you start passing around the constructors but call the general combinators (which all have to handle all cased, even though they are not possible and return more generic types - both of which reinforce the issue with imprecision in types when you deal with construct s as types rather than their content).

> and #2 discouraging pattern matching outright.

This is an interesting question because it has pretty severe practical consequence either way. For simple cases fold is equivalent and superior (better inference and compile time checks), but it has limitations with general recursion. Exposing the constructors directly isn't an option because of the invariants that need to hold, so you would have to define your own extractors (you can do this yourself easily by adding delegating unapply functions for the option combinators (str, number, obj...) - but is unlikely to be something we would add because it lacks compiler checks for users to ensure they match all cased). In general the preferred solution here is to switch to cursor which exposes better ways to do recursion and for parsing case also gives you more power to do what you want.

> I do not know the reason for #1, and I have posted a question about it on StackOverflow. [2] For #2, I think I get the point about combinators being more functional

I would never say it has to do with being "more functional" it is just about dealing with the consequences of exposing these things - in general we refrain from exposing things that are unsafe.

> and better solution (here is the only concrete material I have read that touches on the subject - [3]), but I am not sure if combinators subsume all kind of things that one can do with pattern matching. (Unless you have something as full-fledged as Data.Pattern. [4])

In 6.1 there are prisms which effectively do the same thing. But I think it is more likely a cursor based solution will work for the parser you describe.

>
> But then, I humbly concede that I don't have as much depth in the subject as you all, and would love to have my opinions changed/refined.
>
> Here are some cases I have handpicked from our codebase (sscce-fied [5]), for which the alternatives are not clear to me:
>
> 1. Ad hoc parsing into a custom complex type. The function is recursive, and we are also using regex extractors here.
>
> def parseThrak(json: JsValue): Seq[Thrak] = json match {
>   case JsString(Regex1(x)) => Seq(SimpleText(x))
>   case JsString(Regex2(x)) => Seq(ComplexText(x))
>   case o @ JsObject(_) if o.containsKeys("k1") => Seq(Lov(o))
>   case o @ JsObject(_) if o.containsKeys("k2") => Seq(Uom(o))
>   case JsArray(xs) => xs.flatMap(parseThrak)
>   case x => sys error s"oops: $x"
> }

The best thing here will be to call hcursor and use as[Type] as with ||| to handle multiple cases. Will gist something later.

>
> 2. Recursive processing over arrays and objects.
>
> def processJson(json: JsValue): JsValue = json match {
>    case JsString(str)      => JsString(processString(str))
>    case JsArray(values) => JsArray(values.map(processJson))
>    case JsObject(pairs)  => JsObject(pairs.map { case (k, v) => (k -> processJson(v))})
>    case json                  => json
> }
>

Three options, hcursor, fold, there is a cps'd loop function (on decode result from memory) that can be used to deal with stack issues.

> 3. Effectful functions that deal with JSONs, and which are recursive.
>
> def processJsonFO(json: JsValue): Future[Option[JsValue]] = json match {
>   // ...
> }
>

Same solution, as above /w scalaz sequence.

> 4. Throw away noisy stuff; just keep what is interesting.
>
> jsValues collect {
>   case JsString(x) => x
> }
>

Combinators, I.e. Json.string.

> etc.
>
> Could you please suggest how these things could be done with the combinators provided in Argonaut?

Will gist when I have a machine.

> --
> You received this message because you are subscribed to the Google Groups "argonaut-json" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to argonaut-jso...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Mark Hibberd

unread,
Aug 15, 2014, 5:35:43 PM8/15/14
to Rahul Goma Phulore, argona...@googlegroups.com

(Apologies for the typos/grammar - phones - but hopefully gives some ideas to start with)

"Ionuț G. Stan"

unread,
Aug 22, 2014, 11:34:48 AM8/22/14
to Mark Hibberd, Rahul Goma Phulore, argona...@googlegroups.com
Hi all,

My experience with Argonaut is rather weak, but this is how I'd approach
solving the problems you mentioned Rahul:

https://gist.github.com/igstan/9705afbf40fe5d51feb2


On 16/08/14 00:30, Mark Hibberd wrote:
>
> I not at my machine, so I will have to defer longer answers until later.
> But some detail inline.
>
> On 16/08/2014 6:18 am, "Rahul Goma Phulore" <rahul.ph...@gmail.com
> <mailto:argonaut-json%2Bunsu...@googlegroups.com>.
> > For more options, visit https://groups.google.com/d/optout.
>
> --
> You received this message because you are subscribed to the Google
> Groups "argonaut-json" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to argonaut-jso...@googlegroups.com
> <mailto:argonaut-jso...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.


--
Ionuț G. Stan | http://igstan.ro

missingfaktor

unread,
Aug 24, 2014, 7:02:30 AM8/24/14
to Ionuț G. Stan, Mark Hibberd, argona...@googlegroups.com
Hi Ionuț.

Thank you for the gist! Very helpful.


On Fri, Aug 22, 2014 at 9:04 PM, "Ionuț G. Stan" <ionut....@gmail.com> wrote:
Hi all,

My experience with Argonaut is rather weak, but this is how I'd approach solving the problems you mentioned Rahul:

https://gist.github.com/igstan/9705afbf40fe5d51feb2



On 16/08/14 00:30, Mark Hibberd wrote:

I not at my machine, so I will have to defer longer answers until later.
But some detail inline.

On 16/08/2014 6:18 am, "Rahul Goma Phulore" <rahul.ph...@gmail.com

 > For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google
Groups "argonaut-json" group.
To unsubscribe from this group and stop receiving emails from it, send

For more options, visit https://groups.google.com/d/optout.


--
Ionuț G. Stan  |  http://igstan.ro



--
Cheers,
Rahul.

“And those who were seen dancing were thought to be insane by those who could not hear the music.” ― Friedrich Nietzsche

missingfaktor

unread,
Aug 24, 2014, 7:02:30 AM8/24/14
to Ionuț G. Stan, Mark Hibberd, argona...@googlegroups.com
Hi Mark.

Thank you for your answers. I have only now started playing with cursors and such. Given the size of the part of our codebase using pattern matching, it would be a time consuming (and exploratory) exercise to convert it to cursor style code. If we switch to Argonaut, we will for the time being, define our own extractors and then port the code.

Reply all
Reply to author
Forward
0 new messages