-
Notifications
You must be signed in to change notification settings - Fork 380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiplicity subtyping with dependent types can break linearity #73
Comments
Comment by edwinb Hmm, this is a bit awkward. The subtyping rules are to allow you to pass linear functions to HOFs which don't have linearity constraints (but not vice-versa). But this suggests the rules are too liberal. There's no subtyping rules between 0-multiplicity things and others, so I don't think this can cause erased things to be needed at run time, but if you can use it to pass non-linear functions in a context that requires linearity, that would be bad. |
Comment by KyleDavidE It seems to me that the linearity rules subtyping should only lift over (dependent) function types. In an ideal world it would also lift over positive functors, but I have no idea if that is possible. The haskell-y way to solve this problem would be linearity polymorphism rather than subtyping but I can't imagine terms with linearity polymorphism would be pleasant to reason about. |
Add output file flag (-o) to CLI
I've been trying to use this to break linearity for something concrete, but with no success. I thought
...which is quite safe. The reason you get that is that, thanks to the subtyping rule, the explicit definition of badEq is |
The behavior you are seeing with sym only seems to be happening as a weird product of type inference, here is a fully worked example creating a IO action that produces the unsafePreformIO primitive note that the explicit arguments on This same approach could be used to make a cloner for any type.
|
I had the related issue #743 break things in some real code |
Would this be better done not as subtyping but with an implicit insertion of the following function?
|
@edwinb wrote:
Why is this not really bad already? foo : ((x : a) -> (a,a)) ~=~ ((1 x : a) -> (a,a))
foo = Refl
diagonal : (1 x : a) -> (a,a)
diagonal = let u = replace {p = id} foo in u (\x => (x,x)) |
Ah, I think what @edwinb was doing before was using rewrite not replace and since rewrite requires the equality the other way around. |
I'm still confused. We can get both equations: foo : ((x : a) -> (a,a)) ~=~ ((1 x : a) -> (a,a))
foo = Refl
foo' : ((1 x : a) -> (a,a)) ~=~ ((x : a) -> (a,a))
foo' = let u = replace {p = \w => w ~=~ (x : a) -> (a,a) } foo in u Refl so this can't be about the direction of rewriting... |
Yeah, I think that unless we want to fully embrace the concept of variance which is necessary whenever you have subtyping (I think this would be a mistake) we need to drop subtyping. |
The same logic that allows us to convert a |
Back when I was thinking with this I noticed that most of the time that it seems like the language is stopping you from breaking linearity using this it is actually an issue of type inference not type checking, so if you make everything explicit it can breaks. (This is what causes the illusion of the direction of rewriting mattering) Subtyping is definitely weird but it seems like it seems like if it is to be dropped, something else needs to go in its place (ie. Linearity polymorphism) since it is definitely a real world use case that comes up a ton. Especially if we want people to be able to aggressively declare functions as linear whenever possible. |
It's disappointing to have to do this, but I think necessary because various issue reports have shown it to be unsound (at least as far as inference goes) and, at the very least, confusing. This patch brings us back to the basic rules of QTT. On the one hand, this makes the 1 multiplicity less useful, because it means we can't flag arguments as being used exactly once which would be useful for optimisation purposes as well as precision in the type. On the other hand, it removes some complexity (and a hack) from unification, and has the advantage of being correct! Also, I still consider the 1 multiplicity an experiment. We can still do interesting things like protocol state tracking, which is my primary motivation at least. Ideally, if the 1 multiplicity is going to be more generall useful, we'll need some kind of way of doing multiplicity polymorphism in the future. I don't think subtyping is the way (I've pretty much always come to regret adding some form of subtyping). Fixes idris-lang#73 (and maybe some others).
Since this is an issue of inference, I am wondering if it would be better to extend unification to have "multiplicity variables" which are resolved later in the process. At the moment they have to be solved immediately, which is what causes the guessing. There is a PR to remove subtyping, but I'm reluctant to merge because it is quite limiting. |
Is this an issue of inference? #743 doesn’t seem to be an issue of inference, plus I don’t see inference causing an issue in @ohad’s example:
I think the issue is having subtyping but not variance (and I really don’t want variance, that seems like an enormous headache) |
Well that's awkward. We definitely don't want to have to think about that. We'll probably have to make the multiplicities incompatible then. It's a shame that it'll make some libraries harder to write, but we can't have a way to subvert linearity. |
Would it not be a possibility to have variance only for function args/returns, but not anywhere else (e.g. the equality type) |
It's disappointing to have to do this, but I think necessary because various issue reports have shown it to be unsound (at least as far as inference goes) and, at the very least, confusing. This patch brings us back to the basic rules of QTT. On the one hand, this makes the 1 multiplicity less useful, because it means we can't flag arguments as being used exactly once which would be useful for optimisation purposes as well as precision in the type. On the other hand, it removes some complexity (and a hack) from unification, and has the advantage of being correct! Also, I still consider the 1 multiplicity an experiment. We can still do interesting things like protocol state tracking, which is my primary motivation at least. Ideally, if the 1 multiplicity is going to be more generall useful, we'll need some kind of way of doing multiplicity polymorphism in the future. I don't think subtyping is the way (I've pretty much always come to regret adding some form of subtyping). Fixes idris-lang#73 (and maybe some others).
Issue by KyleDavidE
Friday May 08, 2020 at 15:07 GMT
Originally opened as edwinb/Idris2-boot#357
Steps to Reproduce
Expected Behavior
The term should not typecheck because this can be used to cast
(x: Nat) -> Nat
to(1 x:Nat) -> Nat
, this sort of trick could easily be used to make "safe" terms of typeIO ((0 x: Type) -> IO x -> x)
which could grotesquely break things.Observed Behavior
It type checks, though it is of note that
badEq : ( (1 x:Nat) -> Nat ) = ( (x: Nat) -> Nat)
) it does not type checkThe text was updated successfully, but these errors were encountered: