Constraints in PureScript are elaborated to function arguments and applications. When something has a Partial => ...
signature, it’s like adding another unit-like argument to the function, where a Partial
argument (dictionary) is passed in at the call-site. The signature of unsafePartial
takes a function accepting an implicit Partial
constraint, and explicitly discharges it. That is, it supplies an argument/dictionary for the constraint, giving back the underlying a
value.
unsafePartial :: forall a. (Partial => a) -> a
A desugared version of that signature might be something like:
data PartialDict = PartialDict
unsafePartial :: forall a. (PartialDict -> a) -> a
Note that the Partial
constraint has been replaced with a dictionary, and instead of =>
we have an explicit ->
. The compiler does a translation like this automatically. The implementation essentially then becomes something equivalent to:
unsafePartial :: forall a. (PartialDict -> a) -> a
unsafePartial fn = fn PartialDict
Your particular example can be simplified to something like:
bad :: forall a. a -> a
bad = unsafePartial <<< fromJust <<< Just
good :: forall a. a -> a
good a = unsafePartial (fromJust (Just a))
Now, lets look at the signature of fromJust
:
fromJust :: forall a. Partial => Maybe a -> a
If we apply a similar translation as above:
fromJust :: forall a. PartialDict -> Maybe a -> a
We see that fromJust
is actually a function of two arguments to the compiler. However, function composition only works on a single argument. Lets breakdown the bad
implementation.
- It first lifts a value into
Maybe a
via the Just :: forall a. a -> Maybe a
constructor.
- It then calls
fromJust :: forall a. Partial => Maybe a -> a
on that value.
- It then attempts to remove a
Partial
constraint via unsafePartial :: forall a. (Partial => a) -> a
.
If you look at the signatures of 1 and 2, you’ll see that in order to feed a Maybe a
value into fromJust
, we must first apply a Partial
dictionary. If we were to desugar that:
fromJust somePartialDict <<< Just
Where somePartialDict
is a free variable. That is, we eagerly need to provide a dictionary for Partial
in order to even build our function composition. The type and desugaring of this expression is:
c1 :: forall a. PartialDict -> a -> a
c1 somePartialDict = fromJust somePartialDict <<< Just
bad
is failing because there’s nothing in scope to provide for somePartialDict
. There’s more to it though, and why this wouldn’t be type correct. Just like fromJust
, we have a two-argument function again. At this point we are feeding in an a
value to unsafePartial
(since we’ve already called fromJust
), when it actually needs a function that takes Partial => a
. There are a few alternative solutions:
The good
variant above, which desugars to:
good a = unsafePartial (\somePartialDict -> fromJust somePartialDict (Just a))
The compiler can look at the type unsafePartial, and know that it should elaborate the argument to take a constraint.
You can also do:
good2 = unsafePartial (fromJust <<< Just)
Which elaborates to something similar to the c1
example above.
And a third variant would be to change the signature of fromJust
. Part of the problem is that a Partial
dictionary must be provided first. Instead you could write:
fromJust' :: forall a. Maybe a -> (Partial => a)
and then your original composition would work.
I’d recommend trying these all out on try.purescript.org
and look at the generated JS source to see how the compiler is elaborating the code.