Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON decoders chapter #68

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

monmcguigan
Copy link

@monmcguigan monmcguigan commented Jul 31, 2024

Please have a look at my simple JSON decoder implementation and let me know if I'm making any glaringly obvious mistakes - would love feedback on if how I am doing this is idiomatic or not.

Goals of chapter

I want to show a scale model of how you could JSON decoding (and encoding) in Roc. Doing so will demonstrate using ADTs for modelling your data, pattern matching, function reuse and how to build a small Codec libary.

This article was a great help in thinking about how to teach people these concepts.

Code Structure

Student.roc this is my domain data type of what I want to decode into.

JsonData.roc this is my ADT for JSON. I have purposefully not made it an accurate representation of the JSON data type as it adds a lot of noise for not much gain.

DecodeStudentLong.roc is the verbose version of a JSON decoder where the error handling code and other boilerplate-y code is all tangled up in the business logic. The purpose of it is to highlight lots of the repeated code, so then we can refactor out patterns that we spot.

Decoding.roc this is the meat of what I want to show. I have deocders for my Json to a, along with some helper functions. Still to do would be a decoder for Sum types and Product types (see below for more).

DecodeStudent.roc is the final, much simpler Student decoder, which utilises the Decoding.roc code.

Encoding.roc this is still a work in progress, and isn't going to be included in my talk, but hopefully the chapter (possibly starting with it as it is a simpler thing to get your head around).

json/main.roc here I test both my verbose and simple decoders for Student

TODOs

sumDecoder - I am thinking passing in a list of JsonDecoder a, where a is the super type, and trying each of them till one is successful

productDecoder - I think for this a List (Str JsonDecoder a) would make sense as an input, where each item in the list is the field name and the Json Decoder respectively. But not sure how I can build a record from that and/or how to handle an error if one of the decoders fails.

nameField = field "name" string
creditsField = field "credits" number
enrolledField = field "enrolled" bool
(map3 nameField creditsField enrolledField \(name, credits, enrolled) -> { name: name, credits: credits, enrolled: enrolled }) json
Copy link
Sponsor Contributor

@rtfeldman rtfeldman Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if you want to get into record builder syntax in this chapter (or if it seems too advanced - also, the new version of this syntax just landed in the compiler, so it may not have existed when this part of the chapter was first written!) but it can make this function a lot more concise:

readModule : JsonDecoder Module
readModule = \json ->
    { map2 <-
        name: field "name" string,
        credits: field "credits" number,
        enrolled: field "enrolled" bool,
    } json

By the way, I know we have a compiler bug that means right now you have to do \json -> (...) json (which in general should be able to be written as just (...)), but hopefully we can fix that in time to simplify this to just readModule = { map2 <- ... } without the need for the enclosing lambda!

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, although I think in order for this to work, map2 would need the function it accepts to take 2 arguments instead of a tuple:

-map2 : JsonDecoder a, JsonDecoder b, ((a, b) -> c) -> JsonDecoder c
+map2 : JsonDecoder a, JsonDecoder b, (a, b -> c) -> JsonDecoder c

That's the map2 signature the { foo <- syntax sugar is designed to work with 😄

json/index.md Outdated
---
---
# Goals of chapter
I want to show a scale model of how you could JSON decoding (and encoding) in Roc. Doing so will demonstrate using ADTs for modelling your data, pattern matching, function reuse and how to build a small Codec libary.
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor terminology note for the actual text of the chapter - in all the Roc documentation we talk about "tag unions" rather than referring to them as ADTs (OCaml programmers might point out that they're more like polymorphic variants anyway), so it'd be good to keep consistent with that terminology!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay sure - so does it make sense to say a tag union is a sum of tag types? where each tag can be a value by itself or it can have a payload associated with it?

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say that's accurate, although in my experience anyone who's familiar with the term "sum types" understands them right away and it doesn't matter what explanation I use, so I try to focus on people who are used to languages that don't have any form of the feature. 😄

I like to talk about it in terms of "alternatives" - like "this type could be one of several alternatives..."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right gotcha that makes sense!

json/main.roc Outdated Show resolved Hide resolved
json/main.roc Outdated Show resolved Hide resolved
json/main.roc Outdated Show resolved Hide resolved
json/main.roc Outdated Show resolved Hide resolved
Copy link
Sponsor Contributor

@rtfeldman rtfeldman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking awesome @monmcguigan, I'm really excited about this chapter!!!

Thanks so much for all your excellent work on it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants