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