You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Mar 25, 2021. It is now read-only.
Copy file name to clipboardExpand all lines: README.md
+138-2Lines changed: 138 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,142 @@ This library requires version 0.10 of the PureScript compiler, or later.
13
13
bower install purescript-generics-rep
14
14
```
15
15
16
-
## Documentation
16
+
## Introduction
17
17
18
-
Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-generics-rep).
18
+
The PureScript compiler has supported _generic deriving_ in some form since version 0.7.3, thanks to [Gershom Bazerman](http://gbaz.github.io/). However, since then, it has gone through several iterations, and the current version (implemented in the `purescript-generics-rep` library) makes it possible to have the compiler derive a wide variety of boilerplate code based on types. One example of this is the serialization and deserialization of JSON, and in this post I'll show how to use the [`purescript-foreign-generic`](https://github.com/paf31/purescript-foreign-generic) library to create such functions using generics.
19
+
20
+
### Generics Overview
21
+
22
+
PureScript's generics are supported by the `purescript-generics-rep` library, and in particular, the `Data.Generic.Rep.Generic` type class:
23
+
24
+
```purescript
25
+
class Generic a rep | a -> rep where
26
+
from :: a -> rep
27
+
to :: rep -> a
28
+
```
29
+
30
+
There are three interesting things here:
31
+
32
+
- The `rep` type argument and associated functional dependency define a type function from user types (`a`) to their _representations_ (`rep`).
33
+
-`from` converts a regular value into the representation type.
34
+
-`to` converts a representation value back into a regular value.
35
+
36
+
`purescript-generics-rep` provides standard representation types which can be used to represent any `data` types which can be expressed in PureScript code.
37
+
38
+
It is possible to write out `Generic` instances for our own types by hand, but doing so is very laborious. Instead, we can _derive_ instances by using the `derive` keyword:
39
+
40
+
```purescript
41
+
newtype Person = Person { name :: String, location :: String }
42
+
43
+
derive instance genericPerson :: Generic Person _
44
+
```
45
+
46
+
Note that the second type argument, which represents the representation type, is specified as a type wildcard. This is useful, because representation types can get quite large, so it is inconvenient to type them out by hand in deriving declarations.
47
+
48
+
### Show, Eq, Ord
49
+
50
+
The key insight regarding generics is this: if we can write a function which works with any of the standard representation types, then we implement the same function for any instance of `Generic`. We can even exploit type information in our implementation by using additional type classes to reflect the type information at runtime.
51
+
52
+
`purescript-generics-rep` provides helper functions for implementing common type classes from the Prelude:
53
+
54
+
-`genericShow` gives a default implementation of `show` from the `Show` class
55
+
-`genericEq` gives a default implementation of `eq` from the `Eq` class
56
+
-`genericCompare` gives a default implementation of `compare` from the `Ord` class
57
+
-`genericAppend` gives a default implementation of `append` from the `Semigroup` class
58
+
-`genericMempty` gives a default implementation of `mempty` from the `Monoid` class
59
+
60
+
Using these functions is as simple as dropping the generic implementation into your instances:
61
+
62
+
```purescript
63
+
instance showPerson :: Show Person where
64
+
show = genericShow
65
+
66
+
instance eqPerson :: Eq Person where
67
+
eq = genericEq
68
+
69
+
instance ordPerson :: Ord Person where
70
+
compare = genericCompare
71
+
72
+
instance semigroupPerson :: Semigroup Person where
73
+
append = genericAppend
74
+
```
75
+
76
+
### Handling Foreign Data
77
+
78
+
The `purescript-foreign` library is used in PureScript to handle untrusted external data, and to turn such data into typed values. This functionality is represented by the `Decode` type class in the `purescript-foreign-generic` library:
79
+
80
+
```purescript
81
+
class Decode a where
82
+
decode :: Foreign -> F a
83
+
```
84
+
85
+
`Decode` instances are a good example of boilerplate code. In most cases, we proceed based on the structure of the type in question. For example, here is one possible implementation of `Decode` for our `Person` type, using the new _field puns_ feature:
86
+
87
+
```haskell
88
+
instancedecodePerson::DecodePersonwhere
89
+
decode value =do
90
+
name <- value !"name"
91
+
location <- value !"location"
92
+
pure$Person { name, location }
93
+
```
94
+
95
+
This is not too bad, but real-world records often contain many more fields. Also, it would be nice if we could be sure that the corresponding _encoding_ function would always generate compatible data. Let's see how to verify the same data using `Generic`, which will solve both of these problems.
96
+
97
+
The `purescript-foreign-generic` library defines a function `genericDecode`, with the following type:
98
+
99
+
```haskell
100
+
genericDecode
101
+
::forallarep
102
+
.Genericarep
103
+
=>GenericDecoderep
104
+
=>Options
105
+
->Foreign
106
+
->Fa
107
+
```
108
+
109
+
The `Options` type here is based on the options record from Haskell's`aeson` library.For our purposes, the default options will work, but we need to turn on the `unwrapSingleConstructors` option, so that our `newtype` constructor gets ignored during serialization:
Right (Person { name: "John Smith", location: "USA" })
131
+
```
132
+
133
+
### Generating JSON
134
+
135
+
Just as `genericDecode` can be used to _read_ well-typed data, the `genericEncode` and `genericEncodeJSON` functions can be used to produce the appropriate data or JSON from a PureScript value. The generated `genericDecode` and `genericEncode` functions are inverse to each other for any given input type.
136
+
137
+
In PSCi, we can test JSON generation for our `Person` data type:
One application of this technique is to produce and consume JSON for use with JSON web services, using generics to reduce the amount of boilerplate model code needed.
145
+
146
+
### Performance Concerns
147
+
148
+
Generic deriving can be very convenient for code generation, but it comes with a performance penalty. Consider defining a `Show` instance using `genericShow` - instead of simply converting our data type directly to a `String`, we first convert it to the representation type, and then convert that representation into a `String`. Creating this intermediate structure comes with a cost.
149
+
150
+
Thankfully, the `generics-rep` approach means that we only need to perform a shallow copy of the data, up to the first data constructor or record, so in practice the performance cost is acceptable. In the case of `foreign-generic`, the benefits listed above usually outweight the performance cost, since we rarely need to parse or generate JSON in performance-critical sections of code in many applications.
151
+
152
+
## API Documentation
153
+
154
+
API documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-generics-rep).
0 commit comments