Coercible for FFI

I’ve been using untagged-union on FFI bindings to much success. This has relied on a Coercible class within the lib I think I have to rename to something else.

One of the headline features of purescript 0.14 are compiler solved Coercible instances. See purescript/purescript#3351. However, the Coercible on main purescript is different from untagged-union. In particular, Coercible on purescript 0.14 is symmetric - purescript/purescript#3930 , whereas untagged-union Coercible is after something that’s not. Also, we’re looking at JS runtime specific coercions.

For instance, we have an instance for Coercible Int Number since purescript ints are simply backed by JS numbers. This relationship is not symmetric such that we cannot coerce all Number to Int.

Since Coercible will be central to purescript 0.14, having a separate typeclass with the same name here might be confusing.

Seeking opinions what to rename it to. :slight_smile: Also, wondering if there’s something similar on other languages we can take inspiration from. I’m considering cast instead of coerce.

As an aside, I aim to use this heavily on autogenerated aws-sdk bindings and react component bindings I’ve been working on the past weeks.

3 Likes

Yeah, I think that cast sounds good but as you can guess English is not my primary language :wink:

You could have a class which supports both way casting explicitly:

class Subtype a b where
  upcast :: a -> b

instance subtypeIntNumber :: Subtype Int Number where
  upcast = unsafeCoerce
  
instance subtypeNumberMaybeInt :: Subtype Number (Maybe Int) where
  upcast = fromNumber
  
downcast :: forall b a. Subtype b (Maybe a) => b -> Maybe a
downcast = upcast

The downside being that you can no longer use a global unsafeCoerce implementation for all upcasts

EDIT: Subtype probably isn’t the best name for it, as the second instance wouldn’t make sense

1 Like

A silly solution?

class Subtype a b (isFree :: Boolean) | a b -> isFree where
  upcast :: a -> b

instance subtypeIntNumber :: Subtype Int Number True where
  upcast = unsafeCoerce
  
instance subtypeNumberMaybeInt :: Subtype Number (Maybe Int) False where
  upcast = fromNumber
  
downcast :: forall b a isFree. Subtype b (Maybe a) isFree => b -> Maybe a
downcast = upcast

cast :: forall a b. Subtype a b True => a -> b
cast = unsafeCoerce

The way I position Coercible / Castable on untagged-union is that it’s more of a label and does not implement any functions.

Meaning:

class Coercible a b

coerce :: forall a b. Coercible a b. a -> b
coerce = unsafeCoerce

The reason for this restriction is that I aim for Coercible not to be used in general purescript code. But explicitly only for dealing with FFI. When writing code in purescript, it is better to use explicit ADTs / generics etc.

The reason why I think Coercible / Castable as I defined makes sense here is that coding in purescript deals with producing output code that is a small unconventional subset of the host language - in my case javascript. So to assist with “normal” practices on JS that are not so on purescript, we can use Coercible (ex. optional fields and duck typing, number = integer).

2 Likes

Ah, that makes it clearer. But yes, I agree with cast/upcast.

1 Like