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. 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.
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
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).