quangis / transforge

Describe processes as type transformations, with inference that supports subtypes and parametric polymorphism. Create and query corresponding transformation graphs.
GNU General Public License v3.0
2 stars 0 forks source link

Investigate constraining behaviour #85

Open nsbgn opened 2 years ago

nsbgn commented 2 years ago

Consider this:

    TypeSchema(lambda x, y, z: F(x, x) ** z[F(x, A)]).apply(F(A, A))

... does not yield F(A, A)

    TypeSchema(lambda x, y, z: F(x, x) ** z[F(x, x)]).apply(F(A, A))

does yield F(A, A).

The logic for unifying with constraints doesn't make full sense. Why would it be okay to unify with base types, but not with variables?

Also, we should make sure that the following works:

    TypeSchema(lambda x, y, z: F(x, y) ** z[F(x, A), F(y, A)]).apply(F(A, A))
nsbgn commented 2 years ago

A better way might be to explicitly indicate whether a subtype is acceptable, perhaps by having x[A, B] mean something different from x[+A, B] (where the + indicates that a subtype is accepted. Or perhaps automatically unfold concrete types in constraints to all their subtypes. I need to think through the ramifications.

nsbgn commented 2 years ago

Unfolding the concrete types seems the most workable. I would just need to register the subtypes of every base type operator and add a .subtypes() method on TypeInstances.

nsbgn commented 2 years ago

While the above is a good idea, it becomes complicated when coupled with intersection and union types (#79), since A.subtypes() should include A & B, and A & B & C, and... Though this might not be a problem since we can disregard them.

nsbgn commented 2 years ago

Plan of attack:

Start with tests.

nsbgn commented 2 years ago

We use x[A] to mean that 'once x is bound, we must check that it is subtype of A', and we use x[y,z] to mean that 'once we are sure that x can only possibly unify with one of the constraint options (y or z), we can perform that unification'. This mixes up ideas: If one of the options is bound before the final option unification is triggered, it has turned into a subtype guard and won't unify to a concrete type anymore. If it is bound after, then it has already unified so we immediately get a concrete type. Also, it is unclear what A[x] would mean: must we check that x is a supertype of A once it is bound, or can we immediately bind x>A?

All this does not amount to sane behaviour.

This is why we should only accept the second idea, and when we want to be able to express a subtype guard, we must be explicit about it. How is still up for debate.

(x[~Qlt]) >> x
(x[+Qlt]) >> x
(x < Qlt) >> x
(x[Sub(Qlt)]) >> x
(x[Qlt.sub()]) >> x

x <= Qlt is the most intuitive, but it would need reinterpretation of the < operator, and I don't know if we ever need to combine the two (e.g. x[A, Subtype(B)]).

nsbgn commented 2 years ago

Combining makes sense to do like so: (y < B) & x[A, y] or x[A, _ < B]