I decided to take a stab at implementing what I suggested in this post: Untagged union types
I don’t really expect this to make it into the official compiler, it was mostly a challenge for myself to write more haskell and develop a better understanding of the purescript compiler internals.
The basic idea is that we introduce a new type of constraint (ErasedConstraint), where the constraint must be solved for the RHS of the constraint to be used, but no dictionary is passed. The compiler guarantees to:
- Not defer the computation in the RHS of the constraint (it never produces an abstraction which would normally introduce the instance dictionary, it’s more like a newtype)
- Erase all ErasedConstraints before code gen, rather than applying the dictionary like a normal constraint would
The main motivation for this is for foreign imports which accept multiple types, but don’t depend on being passed instance dictionaries as they do runtime type checking (discussed in the motivating post)
I implemented this by annotating constraints with a Multiplicity, which can either be Unlimited (which is the default behavior today) and Never (meaning the dictionary is never used).
At the moment, all instances declaration produce dictionaries which are Unlimited, and Unlimited instances imply Never instances during entailment (but not the other way around). To achieve this, I modified the type NamedDict
to not require a value at runtime, and therefore InstanceContext
too.
Note that this doesn’t yet change built in classes like Partial, but it should be very simply to add support for them
Example code and the output:
module Main where
import Data.Unit
import Data.Show
class (Show a) <= MyShow a
ex :: forall a. Show a ? a -> Unit
ex a = unit
ex1 :: forall a. Show a => a -> Unit
ex1 a = ex a
ex2 :: forall a. MyShow a ? a -> Unit
ex2 a = ex a
test1 = ex 10
test2 = ex unit
test3 = ex1 10
test4 = ex1 unit
-- This specific example currently infers 2 constraints, when it would ideally
-- only be 1. However, this can also happen with superclass constraints anyway
-- Could be solved by doing entailment in 2 stages, with unlimited first,
-- and then never.
-- Note that you can turn it into 1 with a hint
-- test5 :: forall a. Show a => a -> Unit
test5 a = unit
where
bar = show a
foo = ex a
// Generated by purs version 0.14.0
"use strict";
var Data_Show = require("../Data.Show/index.js");
var Data_Unit = require("../Data.Unit/index.js");
var MyShow = function (Show0) {
this.Show0 = Show0;
};
var ex = function (a) {
return Data_Unit.unit;
};
var ex1 = function (dictShow) {
return function (a) {
return ex(a);
};
};
var ex2 = function (a) {
return ex(a);
};
var test1 = ex(10);
var test2 = ex(Data_Unit.unit);
var test3 = ex1(Data_Show.showInt)(10);
var test4 = ex1(Data_Unit.showUnit)(Data_Unit.unit);
var test5 = function (dictShow) {
return function (a) {
var foo = ex(a);
var bar = Data_Show.show(dictShow)(a);
return Data_Unit.unit;
};
};
module.exports = {
MyShow: MyShow,
ex: ex,
ex1: ex1,
ex2: ex2,
test1: test1,
test2: test2,
test3: test3,
test4: test4,
test5: test5
};