Open ceedubs opened 5 months ago
I suspect this is just because data types are invariant in Unison. List happens to be covariant, so we could special case it in the typechecker, and I could also imagine inferring variance of type params for user defined data types. It could hook in at a similar place as kind inference. Then the typechecker would need to maintain and use this variance info in various places.
Summary: this isnβt an easy βbugfixβ, itβs more like a new type system feature, but I think it could be worth doing at some point.
@pchiusano it doesn't seem to me like you should need lists to be treated as covariant for this code to compile. But I'm not a type system expert, and I'd like to better understand how I'm wrong.
List.foreach : (a -> {g} ()) -> [a] -> ()
In my case a
is '{} ()
. But I don't really need the type system to understand that ['{} ()]
conforms to ['{Exception} ()]
. I just need it to recognize that I can call runThunk
with a '{} ()
.
If I change my main
like this it compiles:
main = do
List.foreach (thunk -> let
thunk' : '{Exception} ()
thunk' = do thunk ()
runThunk thunk'
) thunks
But this still fails:
main = do
List.foreach (thunk -> let
thunk' : '{Exception} ()
thunk' = thunk
runThunk thunk'
) thunks
To me this seems off. The code works fine with thunk' = do thunk ()
, so with thunk' = thunk
(with an explicit type annotation), I would expect it to either compile or to complain that thunk'
has type '{} ()
instead of '{Exception} ()
. I don't understand why instead the type error shows up in the outer scope.
I think Paul is right.
The sort of thing that's going to happen is that a
gets solved to '{Exception} ()
from the first argument to foreach
. Then we check the type of thunks
against List ('{Exception} ())
. This second check is with the ability list in an invariant position. There's a little extra where a slack variable is introduced when solving a
I guess, but that doesn't help this scenario.
Your second example doesn't help because it's just going to solve for thunk
and thunk'
to have the same type, I think, so it works just like with runThunk
. It's again just solving a variable (type of thunk
) with a particular expression (annotation of thunk'
).
The one where you apply thunk
has enough indirection to work differently. There you are solving the type of thunk to () ->{e} a
, then later imposing {e} <= {Exception}
, which defaults to e
not including Exception
, so it's still free to match with your empty ability set in thunks
.
Describe and demonstrate the bug
In the following example when I try to use a
'{} ()
as type'{Exception} ()
, I get a type checker error. But I believe thata -> {} b
is supposed to conform to typea -> {g} b
for allg
.Input:
π
The transcript failed due to an error in the stanza above. The error is:
I found an ability mismatch when checking the application
When trying to match [Unit -> Unit] with [Unit ->{π51, Exception} Unit] the right hand side contained extra abilities: {π51, Exception}