Skip to content

Commit 1f78e83

Browse files
committed
Merge pull request #8 from hdgarrood/docs
Add some docs
2 parents aa21be9 + 565c7e6 commit 1f78e83

File tree

2 files changed

+235
-1
lines changed

2 files changed

+235
-1
lines changed

README.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,198 @@ bower install purescript-argonaut-core
1616
## Documentation
1717

1818
Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-argonaut-core).
19+
20+
## Tutorial
21+
22+
Some of Argonaut's functions might seem a bit arcane at first, so it can help
23+
to understand the underlying design decisions which make it the way it is.
24+
25+
One approach for modelling JSON values would be to define an algebraic data
26+
type, like this:
27+
28+
```purescript
29+
data Json
30+
= JNull
31+
| JString String
32+
| JNumber Number
33+
| JBoolean Boolean
34+
| JArray (Array Json)
35+
| JObject (StrMap Json)
36+
```
37+
38+
And indeed, some might even say this is the obvious approach.
39+
40+
Because Argonaut is written with the compilation target of JavaScript in mind,
41+
it takes a slightly different approach, which is to reuse the existing data
42+
types which JavaScript already provides. This way, the result of JavaScript's
43+
`JSON.parse` function is already a `Json` value, and no extra processing is
44+
needed before you can start operating on it. This ought to help your program
45+
both in terms of speed and memory churn.
46+
47+
Much of the design of Argonaut follows naturally from this design decision.
48+
49+
### Types
50+
51+
The most important type in this library is, of course, `Json`, which is the
52+
type of JSON data in its native JavaScript representation.
53+
54+
As the (hypothetical) algebraic data type declaration above indicates, there
55+
are six possibilities for a JSON value: it can be `null`, a string, a number, a
56+
boolean, an array of JSON values, or an object mapping string keys to JSON
57+
values.
58+
59+
For convenience, and to ensure that values have the appropriate underlying
60+
data representations, Argonaut also declares types for each of these individual
61+
possibilities, whose names correspond to the data constructor names above.
62+
63+
Therefore, `JString`, `JNumber`, and `JBoolean` are synonyms for the primitive
64+
PureScript types `String`, `Number`, and `Boolean` respectively; `JArray` is a
65+
synonym for `Array Json`; and `JObject` is a synonym for `StrMap Json`.
66+
Argonaut defines a type `JNull` as the type of the `null` value in JavaScript.
67+
68+
### Introducing Json values
69+
70+
(Or, where do `Json` values come from?)
71+
72+
If your program is receiving JSON data as a string, you probably want the
73+
`parseJson` function in `Data.Argonaut.Parser`, which is a very simple wrapper
74+
around JavaScript's `JSON.parse`.
75+
76+
Otherwise, `Json` values can be introduced into your program via the FFI or via
77+
the construction functions in `Data.Argonaut.Core`. Here are some examples:
78+
79+
```javascript
80+
// In an FFI module.
81+
exports.someNumber = 23.6;
82+
exports.someBoolean = false;
83+
exports.someObject = {people: [{name: "john"}, {name: "jane"}], common_interests: []};
84+
```
85+
86+
```purescript
87+
foreign import someNumber :: Json
88+
foreign import someBoolean :: Json
89+
foreign import someObject :: Json
90+
```
91+
92+
Generally, if a JavaScript value could be returned from a call to `JSON.parse`,
93+
it's fine to import it from the FFI as `Json`. So, for example, objects,
94+
booleans, numbers, strings, and arrays are all fine, but functions are not.
95+
96+
The construction functions (that is, `fromX`, or `jsonX`) can be used as
97+
follows:
98+
99+
```purescript
100+
import Data.Tuple (Tuple(..))
101+
import Data.StrMap as StrMap
102+
import Data.Argonaut.Core as A
103+
104+
someNumber = A.fromNumber 23.6
105+
someBoolean = A.fromBoolean false
106+
someObject = A.fromObject (StrMap.fromFoldable [
107+
Tuple "people" (A.fromArray [
108+
A.jsonSingletonObject "name" (A.fromString "john"),
109+
A.jsonSingletonObject "name" (A.fromString "jane")
110+
]),
111+
Tuple "common_interests" A.jsonEmptyArray
112+
])
113+
```
114+
115+
### Eliminating/matching on `Json` values
116+
117+
We can perform case analysis for `Json` values using the `foldJson` function.
118+
This function is necessary because `Json` is not an algebraic data type. If
119+
`Json` were an algebraic data type, we would not have as much need for this
120+
function, because we could perform pattern matching with a `case ... of`
121+
expression instead.
122+
123+
The type of `foldJson` is:
124+
125+
```purescript
126+
foldJson :: forall a.
127+
(JNull -> a) -> (JBoolean -> a) -> (JNumber -> a) ->
128+
(JString -> a) -> (JArray -> a) -> (JObject -> a) ->
129+
Json -> a
130+
```
131+
132+
That is, `foldJson` takes six functions, which all must return values of some
133+
particular type `a`, together with one `Json` value. `foldJson` itself also
134+
returns a value of the same type `a`.
135+
136+
A use of `foldJson` is very similar to a `case ... of` expression, as it allows
137+
you to handle each of the six possibilities for the `Json` value you passed in.
138+
Thinking of it this way, each of the six function arguments is like one of the
139+
case alternatives. Just like in a `case ... of` expression, the final value
140+
that the whole expression evaluates to comes from evaluating exactly one of the
141+
'alternatives' (functions) that you pass in. In fact, you can tell that this
142+
is the case just by looking at the type signature of `foldJson`, because of a
143+
property called *parametricity* (although a deeper explanation of parametricity
144+
is outside the scope of this tutorial).
145+
146+
For example, imagine we had the following values defined in JavaScript and
147+
imported via the FFI:
148+
149+
```javascript
150+
exports.anotherNumber = 0.0;
151+
exports.anotherArray = [0.0, {foo: 'bar'}, false];
152+
exports.anotherObject = {foo: 1, bar: [2,2]};
153+
```
154+
155+
Then we can match on them in PureScript using `foldJson`:
156+
157+
```purescript
158+
foreign import anotherNumber :: Json
159+
foreign import anotherArray :: Json
160+
foreign import anotherObject :: Json
161+
162+
basicInfo :: Json -> String
163+
basicInfo = foldJson
164+
(const "It was null")
165+
(\b -> "Got a boolean: " <>
166+
if b then "it was true!" else "It was false.")
167+
(\x -> "Got a number: " <> show x)
168+
(\s -> "Got a string, which was " <> Data.String.length s <>
169+
" characters long.")
170+
(\xs -> "Got an array, which had " <> Data.Array.length xs <>
171+
" items.")
172+
(\obj -> "Got an object, which had " <> Data.StrMap.size obj <>
173+
" items.")
174+
```
175+
176+
```purescript
177+
basicInfo anotherNumber -- => "Got a number: 0.0"
178+
basicInfo anotherArray -- => "Got an array, which had 3 items."
179+
basicInfo anotherObject -- => "Got an object, which had 2 items."
180+
```
181+
182+
`foldJson` is the fundamental function for pattern matching on `Json` values;
183+
any kind of pattern matching you might want to do can be done with `foldJson`.
184+
185+
However, `foldJson` is not always comfortable to use, so Argonaut provides a
186+
few other simpler versions for convenience. For example, the `foldJsonX`
187+
functions can be used to match on a specific type. The first argument acts as a
188+
default value, to be used if the `Json` value turned out not to be that type.
189+
For example, we can write a function which tests whether a JSON value is the
190+
string "lol" like this:
191+
192+
```purescript
193+
foldJsonString :: forall a. a -> (JString -> a) -> Json -> a
194+
195+
isJsonLol = foldJsonString false (_ == "lol")
196+
```
197+
198+
If the `Json` value is not a string, the default `false` is used. Otherwise,
199+
we test whether the string is equal to "lol".
200+
201+
The `toX` functions also occupy a similar role: they attempt to convert `Json`
202+
values into a specific type. If the json value you provide is of the right
203+
type, you'll get a `Just` value. Otherwise, you'll get `Nothing`. For example,
204+
we could have written `isJsonLol` like this, too:
205+
206+
```purescript
207+
toString :: Json -> Maybe JString
208+
209+
isJsonLol json =
210+
case toString json of
211+
Just str -> str == "lol"
212+
Nothing -> false
213+
```

src/Data/Argonaut/Core.purs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
-- | This module defines a data type and various functions for creating and
2+
-- | manipulating JSON values. The README contains additional documentation
3+
-- | for this module.
14
module Data.Argonaut.Core
25
( Json(..)
36
, JNull(..)
@@ -50,37 +53,73 @@ import Data.Function
5053

5154
import qualified Data.StrMap as M
5255

56+
-- | A Boolean value inside some JSON data. Note that this type is exactly the
57+
-- | same as the primitive `Boolean` type; this synonym acts only to help
58+
-- | indicate intent.
5359
type JBoolean = Boolean
60+
61+
-- | A Number value inside some JSON data. Note that this type is exactly the
62+
-- | same as the primitive `Number` type; this synonym acts only to help
63+
-- | indicate intent.
5464
type JNumber = Number
65+
66+
-- | A String value inside some JSON data. Note that this type is exactly the
67+
-- | same as the primitive `String` type; this synonym acts only to help
68+
-- | indicate intent.
5569
type JString = String
56-
type JAssoc = Tuple String Json
70+
71+
-- | A JSON array; an array containing `Json` values.
5772
type JArray = Array Json
73+
74+
-- | A JSON object; a JavaScript object containing `Json` values.
5875
type JObject = M.StrMap Json
5976

77+
type JAssoc = Tuple String Json
78+
79+
-- | The type of null values inside JSON data. There is exactly one value of
80+
-- | this type: in JavaScript, it is written `null`. This module exports this
81+
-- | value as `jsonNull`.
6082
foreign import data JNull :: *
83+
84+
-- | The type of JSON data. The underlying representation is the same as what
85+
-- | would be returned from JavaScript's `JSON.stringify` function; that is,
86+
-- | ordinary JavaScript booleans, strings, arrays, objects, etc.
6187
foreign import data Json :: *
6288

89+
-- | Case analysis for `Json` values. See the README for more information.
6390
foldJson :: forall a.
6491
(JNull -> a) -> (JBoolean -> a) -> (JNumber -> a) ->
6592
(JString -> a) -> (JArray -> a) -> (JObject -> a) ->
6693
Json -> a
6794
foldJson a b c d e f json = runFn7 _foldJson a b c d e f json
6895

96+
-- | A simpler version of `foldJson` which accepts a callback for when the
97+
-- | `Json` argument was null, and a default value for all other cases.
6998
foldJsonNull :: forall a. a -> (JNull -> a) -> Json -> a
7099
foldJsonNull d f j = runFn7 _foldJson f (const d) (const d) (const d) (const d) (const d) j
71100

101+
-- | A simpler version of `foldJson` which accepts a callback for when the
102+
-- | `Json` argument was a `Boolean`, and a default value for all other cases.
72103
foldJsonBoolean :: forall a. a -> (JBoolean -> a) -> Json -> a
73104
foldJsonBoolean d f j = runFn7 _foldJson (const d) f (const d) (const d) (const d) (const d) j
74105

106+
-- | A simpler version of `foldJson` which accepts a callback for when the
107+
-- | `Json` argument was a `Number`, and a default value for all other cases.
75108
foldJsonNumber :: forall a. a -> (JNumber -> a) -> Json -> a
76109
foldJsonNumber d f j = runFn7 _foldJson (const d) (const d) f (const d) (const d) (const d) j
77110

111+
-- | A simpler version of `foldJson` which accepts a callback for when the
112+
-- | `Json` argument was a `String`, and a default value for all other cases.
78113
foldJsonString :: forall a. a -> (JString -> a) -> Json -> a
79114
foldJsonString d f j = runFn7 _foldJson (const d) (const d) (const d) f (const d) (const d) j
80115

116+
-- | A simpler version of `foldJson` which accepts a callback for when the
117+
-- | `Json` argument was a `JArray`, and a default value for all other cases.
81118
foldJsonArray :: forall a. a -> (JArray -> a) -> Json -> a
82119
foldJsonArray d f j = runFn7 _foldJson (const d) (const d) (const d) (const d) f (const d) j
83120

121+
-- | A simpler version of `foldJson` which accepts a callback for when the
122+
-- | `Json` argument was a `JObject`, and a default value for all other cases.
84123
foldJsonObject :: forall a. a -> (JObject -> a) -> Json -> a
85124
foldJsonObject d f j = runFn7 _foldJson (const d) (const d) (const d) (const d) (const d) f j
86125

0 commit comments

Comments
 (0)