On Partial and Composition

Hi!

I’m using seriously Purescript for a personal project since few days, and I’m playing with the language. I had a question when I started Firebase interop. To give a little bit of context, when using Firebase Firestore, you can have timestamp, which are server timestamps, encoded in Firebase Timestamp (a native JS object) which can be turned into a Date.
I managed a lot of things with Timestamps, and I wanted to make a function to convert them from Timestamp to Date. I managed to do it without too much pain, but here’s come the question.
At first, I wrote this:

For the foreign.

foreign import foreignTimestampToDate :: Timestamp -> JSDate

And the code:

timestampToDate :: Timestamp -> Date
timestampToDate =
  unsafePartial
  <<< fromJust
  <<< JSDate.toDate
  <<< foreignTimestampToDate

The compiler told me it’s an error, because of something Partial. I didn’t get the error, and tried this:

timestampToDate :: Timestamp -> Date
timestampToDate date =
  unsafePartial
  $ fromJust
  $ JSDate.toDate
  $ foreignTimestampToDate date

And this code is working perfectly.

What is the difference between the two functions? I don’t understand the difference.

1 Like

Yes, this has bitten me before too, however we can see the difference on the REPL:

> import Prelude ((<<<), ($))
> import Data.Maybe (fromJust)
> import Partial.Unsafe (unsafePartial)
> :t unsafePartial <<< fromJust
forall t2. Partial => Maybe (Partial => t2) -> t2
> :t unsafePartial $ fromJust
forall t4. Maybe t4 -> t4

So it’s clear you want the latter. Why the constraints are solved differently by the type checker between (<<<) and ($) I’m not quite sure…

1 Like

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.

  1. It first lifts a value into Maybe a via the Just :: forall a. a -> Maybe a constructor.
  2. It then calls fromJust :: forall a. Partial => Maybe a -> a on that value.
  3. 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.

5 Likes

(Posting this before I have read what Nate posted, but I think it might complement his)

To give some more intuition as to how the types work, I think it’s useful to write a compose that does work for the case

myCompose2 :: forall a b c. ((Partial => b) -> c) -> (Partial => a -> b) -> (a -> c)
myCompose2 f g x = f (g x)

myCompose1 :: forall a b c. (b -> c) -> (a -> b) -> (a -> c)
myCompose1 f g x = f (g x)

-- using myCompose1 won't work
func :: forall x. Maybe x -> x
func = unsafePartial `myCompose2` fromJust

Looking at the compiler output, we can see the runtime representations of each compose are different and therefore not interchangeable (although just because they look different doesn’t mean they are, not sure how to prove it):

var myCompose2 = function (f) {
    return function (g) {
        return function (x) {
            return f(function (dictPartial) {
                return g()(x);
            });
        };
    };
};

var myCompose1 = function (f) {
    return function (g) {
        return function (x) {
            return f(g(x));
        };
    };
};

1 Like

Wow, thank you very much for your answers. I wasn’t expecting so much. It’s awesome.

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.

That’s effectively a good idea I didn’t thought about. I’ll play a little bit with this and try to see how the compiler is generating JS.

1 Like