Skip to content

Commit 4850293

Browse files
committed
Add guide from docs repo to the README
1 parent be7e07c commit 4850293

File tree

1 file changed

+210
-2
lines changed

1 file changed

+210
-2
lines changed

README.md

Lines changed: 210 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,214 @@ Utilities for working with partial functions.
1111
bower install purescript-partial
1212
```
1313

14-
## Documentation
14+
## Why have a Partial type class?
1515

16-
Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-partial).
16+
Every now and then, you will want to use *partial functions;* that is,
17+
functions which don't handle every possible case of their inputs. For example,
18+
there is a function `fromJust :: ∀ a. Partial ⇒ Maybe a → a` in `Data.Maybe`,
19+
which gives you the value inside a `Just` value, or throws an error if given
20+
`Nothing`.
21+
22+
It's important that types tell the truth wherever possible, because this is a
23+
large part of what allows us to understand PureScript code easily and refactor
24+
it fearlessly. However, in certain contexts, you know that e.g. an `Either`
25+
value is always going to be `Right`, but you can't prove that to the type
26+
checker, and so you want an escape hatch so that you can write a function that
27+
doesn't have to deal with the `Left` case. This is often the case when
28+
performance is important, for instance.
29+
30+
Previously, partial functions have been indicated by putting the word "unsafe"
31+
at the start of their names, or by putting them in an "Unsafe" module. For
32+
instance, there was previously an `unsafeIndex` function in
33+
`Data.Array.Unsafe`, and `fromJust` used to be in `Data.Maybe.Unsafe`. However,
34+
this is not ideal, because the fact that these functions are partial, and
35+
therefore unsafe if used carelessly, does not appear in the type. Consequently,
36+
there is little to stop you from using it in an inappropriate manner by
37+
accident.
38+
39+
The Partial type class allows us to put this information back into the types,
40+
and thereby allows us to clearly demarcate which parts of your code are
41+
responsible for making that sure unsafe functions are used in a safe manner.
42+
43+
## I just want to use a partial function, please
44+
45+
If you try to just use a partial function, you'll most likely get an error
46+
about no instance being found for the `Partial` class. Take this program, for
47+
instance:
48+
49+
```purescript
50+
module Main where
51+
52+
import Prelude
53+
import Data.Maybe (Maybe(..), fromJust)
54+
import Effect (Effect)
55+
import Effect.Console (logShow)
56+
57+
main :: Effect Unit
58+
main = logShow (fromJust (Just 3))
59+
```
60+
61+
Because `fromJust` is partial, and because the partiality hasn't been
62+
explicitly handled, you'll get an error:
63+
64+
```
65+
at src/Main.purs line 8, column 1 - line 8, column 56
66+
67+
No type class instance was found for
68+
69+
Prim.Partial
70+
```
71+
72+
*Aside: Yes, this is not a fantastic error. It's going to get better soon.*
73+
74+
The solution is usually to add an application of `unsafePartial` somewhere,
75+
like this:
76+
77+
```purescript
78+
module Main where
79+
80+
import Prelude
81+
import Data.Maybe (Maybe(..), fromJust)
82+
import Effect (Effect)
83+
import Effect.Console (logShow)
84+
import Partial.Unsafe (unsafePartial)
85+
86+
main :: Effect Unit
87+
main = logShow (unsafePartial (fromJust (Just 3)))
88+
```
89+
90+
## Where should I put unsafePartial?
91+
92+
The rule of thumb is to put `unsafePartial` at the level of your program such
93+
that the types tell the truth, and the part of your program responsible for
94+
making sure a use of a partial function is safe is also the part where the
95+
`unsafePartial` is. This is perhaps best demonstrated with an example.
96+
97+
Imagine that we want to represent vectors in 3D with an array containing
98+
exactly 3 values (perhaps we want to use them with some other API that expects
99+
this representation, and we don't want to be converting back and forth all the
100+
time). In this case, we would usually use a `newtype` and avoid exporting the
101+
constructor:
102+
103+
```purescript
104+
module Data.V3
105+
( V3()
106+
, makeV3
107+
, runV3
108+
) where
109+
110+
newtype V3 = V3 (Array Number)
111+
112+
makeV3 :: Number -> Number -> Number -> V3
113+
makeV3 x y z = V3 [x, y, z]
114+
115+
runV3 :: V3 -> Array Number
116+
runV3 (V3 v) = v
117+
```
118+
119+
This way, all of the functions are safe; the code will guarantee that any `V3`
120+
does contain exactly 3 values (although the type checker is not aware of this).
121+
122+
Now imagine we want to write a dot product function:
123+
124+
```purescript
125+
dot :: V3 -> V3 -> Number
126+
dot (V3 [x1, x2, x3]) (V3 [y1, y2, y3]) = x1*y1 + x2*y2 + x3*y3
127+
```
128+
129+
We know this is ok, but the compiler disallows it:
130+
131+
```
132+
A case expression could not be determined to cover all inputs.
133+
The following additional cases are required to cover all inputs:
134+
135+
(V3 _) _
136+
_ (V3 _)
137+
138+
Alternatively, add a Partial constraint to the type of the enclosing value.
139+
140+
in value declaration dot
141+
```
142+
143+
In this case, we can use `unsafePartial` to explicitly say that we don't
144+
actually need to worry about those other cases, and therefore we don't want to
145+
propagate a `Partial` constraint; users of this `dot` function should not have
146+
to worry about this partiality. For example:
147+
148+
```purescript
149+
dot :: V3 -> V3 -> Number
150+
dot x y = Partial.Unsafe.unsafePartial (go x y)
151+
where
152+
go :: Partial => V3 -> V3 -> Number
153+
go (V3 [x1, x2, x3]) (V3 [y1, y2, y3]) = x1*y1 + x2*y2 + x3*y3
154+
-- This second pattern can be omitted, but provides a better error message
155+
-- in case we do get an invalid argument at runtime.
156+
go _ _ = Partial.crash "Bad argument: expected exactly 3 elements."
157+
```
158+
159+
The `unsafePartial` function comes from the `Partial.Unsafe` module, in the
160+
`purescript-partial` package.
161+
162+
In this case, we could also use `Partial.Unsafe.unsafeCrashWith`:
163+
164+
```purescript
165+
dot :: V3 -> V3 -> Number
166+
dot (V3 [x1, x2, x3]) (V3 [y1, y2, y3]) = x1*y1 + x2*y2 + x3*y3
167+
dot _ _ = unsafeCrashWith "Bad argument: expected exactly 3 elements."
168+
```
169+
170+
Both implementations will behave in the same way.
171+
172+
In this case, we know our `dot` implementation is fine, and so users of it
173+
should not have to worry about its partiality, so it makes sense to avoid
174+
propagating the constraint. Now, we will see another case where a `Partial`
175+
constraint *should* be propagated.
176+
177+
Let us suppose we want a `foldr1` function, which works in a very similar way
178+
to `foldr` on Lists, except that it doesn't require an initial value to be
179+
passed, and instead requires that the list argument contains at least one
180+
element.
181+
182+
We can implement it like this:
183+
184+
```purescript
185+
foldr1 f (Cons x xs) = foldr f x xs
186+
```
187+
188+
The compiler infers the correct type here, which is:
189+
190+
```purescript
191+
foldr1 :: forall a. Partial => (a -> a -> a) -> List a -> a
192+
```
193+
194+
Now imagine we want a version of `Data.Foldable.minimum` which returns an `a`
195+
instead of a `Maybe a`, and is therefore partial. We can implement it in terms
196+
of our new `foldr1` function:
197+
198+
```purescript
199+
minimumP = foldr1 min
200+
```
201+
202+
Again, the compiler infers the correct type:
203+
204+
```purescript
205+
minimumP :: forall a. (Partial, Ord a) => List a -> a
206+
```
207+
208+
Notice that the `Partial` constraint is automatically propagated to the
209+
`minimumP` function because of the use of another partial function in its
210+
definition, namely `foldr1`. In this case, this is what we want; we should
211+
propagate the `Partial` constraint, because it is still the caller's
212+
responsibility to make sure they supply a non-empty list.
213+
214+
So hopefully it is now clear why this partiality checking is implemented in
215+
terms of a type class: it allows us to elegantly reuse existing machinery in
216+
the type checker in order to check that a Partial constraint is either
217+
explictly handled or propagated. This should help ensure that when you're
218+
reading the code a few months later, it remains clear which part of the code is
219+
responsible for ensuring that any assumed invariants which cannot be encoded in
220+
the type system do hold.
221+
222+
## API Documentation
223+
224+
* API documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-partial).

0 commit comments

Comments
 (0)