-
Notifications
You must be signed in to change notification settings - Fork 29
Add some docs #8
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,198 @@ bower install purescript-argonaut-core | |
## Documentation | ||
|
||
Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-argonaut-core). | ||
|
||
## Tutorial | ||
|
||
Some of Argonaut's functions might seem a bit arcane at first, so it can help | ||
to understand the underlying design decisions which make it the way it is. | ||
|
||
One approach for modelling JSON values would be to define an algebraic data | ||
type, like this: | ||
|
||
```purescript | ||
data Json | ||
= JNull | ||
| JString String | ||
| JNumber Number | ||
| JBoolean Boolean | ||
| JArray (Array Json) | ||
| JObject (StrMap Json) | ||
``` | ||
|
||
And indeed, some might even say this is the obvious approach. | ||
|
||
Because Argonaut is written with the compilation target of JavaScript in mind, | ||
it takes a slightly different approach, which is to reuse the existing data | ||
types which JavaScript already provides. This way, the result of JavaScript's | ||
`JSON.parse` function is already a `Json` value, and no extra processing is | ||
needed before you can start operating on it. This ought to help your program | ||
both in terms of speed and memory churn. | ||
|
||
Much of the design of Argonaut follows naturally from this design decision. | ||
|
||
### Types | ||
|
||
The most important type in this library is, of course, `Json`, which is the | ||
type of JSON data in its native JavaScript representation. | ||
|
||
As the (hypothetical) algebraic data type declaration above indicates, there | ||
are six possibilities for a JSON value: it can be `null`, a string, a number, a | ||
boolean, an array of JSON values, or an object mapping string keys to JSON | ||
values. | ||
|
||
For convenience, and to ensure that values have the appropriate underlying | ||
data representations, Argonaut also declares types for each of these individual | ||
possibilities, whose names correspond to the data constructor names above. | ||
|
||
Therefore, `JString`, `JNumber`, and `JBoolean` are synonyms for the primitive | ||
PureScript types `String`, `Number`, and `Boolean` respectively; `JArray` is a | ||
synonym for `Array Json`; and `JObject` is a synonym for `StrMap Json`. | ||
Argonaut defines a type `JNull` as the type of the `null` value in JavaScript. | ||
|
||
### Introducing Json values | ||
|
||
(Or, where do `Json` values come from?) | ||
|
||
If your program is receiving JSON data as a string, you probably want the | ||
`parseJson` function in `Data.Argonaut.Parser`, which is a very simple wrapper | ||
around JavaScript's `JSON.parse`. | ||
|
||
Otherwise, `Json` values can be introduced into your program via the FFI or via | ||
the construction functions in `Data.Argonaut.Core`. Here are some examples: | ||
|
||
```javascript | ||
// In an FFI module. | ||
exports.someNumber = 23.6; | ||
exports.someBoolean = false; | ||
exports.someObject = {people: [{name: "john"}, {name: "jane"}], common_interests: []}; | ||
``` | ||
|
||
```purescript | ||
foreign import someNumber :: Json | ||
foreign import someBoolean :: Json | ||
foreign import someObject :: Json | ||
``` | ||
|
||
Generally, if a JavaScript value could be returned from a call to `JSON.parse`, | ||
it's fine to import it from the FFI as `Json`. So, for example, objects, | ||
booleans, numbers, strings, and arrays are all fine, but functions are not. | ||
|
||
The construction functions (that is, `fromX`, or `jsonX`) can be used as | ||
follows: | ||
|
||
```purescript | ||
import Data.Tuple (Tuple(..)) | ||
import Data.StrMap as StrMap | ||
import Data.Argonaut.Core as A | ||
|
||
someNumber = A.fromNumber 23.6 | ||
someBoolean = A.fromBoolean false | ||
someObject = A.fromObject (StrMap.fromFoldable [ | ||
Tuple "people" (A.fromArray [ | ||
A.jsonSingletonObject "name" (A.fromString "john"), | ||
A.jsonSingletonObject "name" (A.fromString "jane") | ||
]), | ||
Tuple "common_interests" A.jsonEmptyArray | ||
]) | ||
``` | ||
|
||
### Eliminating/matching on `Json` values | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm - the following section I found a bit tricky. It absolutely helped me (at least I think so) to understand why I would use My suggestion is to restructure it as follows
I hope my thoughts are correct and make sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this makes sense. One thing though is that I don't think the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's best to use idiomatic purescript code in examples where possible, because people will often base their own code around examples, so I think I'd prefer to leave the operator section There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely agree re a bigger example. I'll also try writing this in a sort of reversed order like you suggested and see how it turns out. Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not suggesting taking that path to explain it here, I'm just using it as an example to show that the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ohh. yeah than that makes sense. can you point me to the discussion about why this has been changed (or will be in the near future)? thx There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and more code examples are always better anyways ;-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @robkuz the discussion was long and tortuous, so the short version: it makes Underscores are used as they are a PureScript idiom for placeholders that can be used in a few places: record accessors, record constructors, record updates, and as of the latest compiler, in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is a link to it in the compiler release notes from one of the 0.8.x releases, look for something like "operator sections". |
||
|
||
We can perform case analysis for `Json` values using the `foldJson` function. | ||
This function is necessary because `Json` is not an algebraic data type. If | ||
`Json` were an algebraic data type, we would not have as much need for this | ||
function, because we could perform pattern matching with a `case ... of` | ||
expression instead. | ||
|
||
The type of `foldJson` is: | ||
|
||
```purescript | ||
foldJson :: forall a. | ||
(JNull -> a) -> (JBoolean -> a) -> (JNumber -> a) -> | ||
(JString -> a) -> (JArray -> a) -> (JObject -> a) -> | ||
Json -> a | ||
``` | ||
|
||
That is, `foldJson` takes six functions, which all must return values of some | ||
particular type `a`, together with one `Json` value. `foldJson` itself also | ||
returns a value of the same type `a`. | ||
|
||
A use of `foldJson` is very similar to a `case ... of` expression, as it allows | ||
you to handle each of the six possibilities for the `Json` value you passed in. | ||
Thinking of it this way, each of the six function arguments is like one of the | ||
case alternatives. Just like in a `case ... of` expression, the final value | ||
that the whole expression evaluates to comes from evaluating exactly one of the | ||
'alternatives' (functions) that you pass in. In fact, you can tell that this | ||
is the case just by looking at the type signature of `foldJson`, because of a | ||
property called *parametricity* (although a deeper explanation of parametricity | ||
is outside the scope of this tutorial). | ||
|
||
For example, imagine we had the following values defined in JavaScript and | ||
imported via the FFI: | ||
|
||
```javascript | ||
exports.anotherNumber = 0.0; | ||
exports.anotherArray = [0.0, {foo: 'bar'}, false]; | ||
exports.anotherObject = {foo: 1, bar: [2,2]}; | ||
``` | ||
|
||
Then we can match on them in PureScript using `foldJson`: | ||
|
||
```purescript | ||
foreign import anotherNumber :: Json | ||
foreign import anotherArray :: Json | ||
foreign import anotherObject :: Json | ||
|
||
basicInfo :: Json -> String | ||
basicInfo = foldJson | ||
(const "It was null") | ||
(\b -> "Got a boolean: " <> | ||
if b then "it was true!" else "It was false.") | ||
(\x -> "Got a number: " <> show x) | ||
(\s -> "Got a string, which was " <> Data.String.length s <> | ||
" characters long.") | ||
(\xs -> "Got an array, which had " <> Data.Array.length xs <> | ||
" items.") | ||
(\obj -> "Got an object, which had " <> Data.StrMap.size obj <> | ||
" items.") | ||
``` | ||
|
||
```purescript | ||
basicInfo anotherNumber -- => "Got a number: 0.0" | ||
basicInfo anotherArray -- => "Got an array, which had 3 items." | ||
basicInfo anotherObject -- => "Got an object, which had 2 items." | ||
``` | ||
|
||
`foldJson` is the fundamental function for pattern matching on `Json` values; | ||
any kind of pattern matching you might want to do can be done with `foldJson`. | ||
|
||
However, `foldJson` is not always comfortable to use, so Argonaut provides a | ||
few other simpler versions for convenience. For example, the `foldJsonX` | ||
functions can be used to match on a specific type. The first argument acts as a | ||
default value, to be used if the `Json` value turned out not to be that type. | ||
For example, we can write a function which tests whether a JSON value is the | ||
string "lol" like this: | ||
|
||
```purescript | ||
foldJsonString :: forall a. a -> (JString -> a) -> Json -> a | ||
|
||
isJsonLol = foldJsonString false (_ == "lol") | ||
``` | ||
|
||
If the `Json` value is not a string, the default `false` is used. Otherwise, | ||
we test whether the string is equal to "lol". | ||
|
||
The `toX` functions also occupy a similar role: they attempt to convert `Json` | ||
values into a specific type. If the json value you provide is of the right | ||
type, you'll get a `Just` value. Otherwise, you'll get `Nothing`. For example, | ||
we could have written `isJsonLol` like this, too: | ||
|
||
```purescript | ||
toString :: Json -> Maybe JString | ||
|
||
isJsonLol json = | ||
case toString json of | ||
Just str -> str == "lol" | ||
Nothing -> false | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this opening - it sets the mindset the reader should have.
Alas I would name the data constructors differently as there a indeed types with names you are using here. Yes I know its only type synonyms yet it could easily confuse when you look into Pursuit and find exactly those types.
What about
JSNull, JSBoolean ...
Or even betterNullWrapper, BooleanWrapper ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor names do correspond exactly to the real types which have those names, though, so I think it makes sense to use the same names. It does also say that Argonaut does not use this approach in the next paragraph.