Skip to content
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

Terminology question #474

Open
martinberger opened this issue Mar 15, 2024 · 9 comments
Open

Terminology question #474

martinberger opened this issue Mar 15, 2024 · 9 comments

Comments

@martinberger
Copy link

martinberger commented Mar 15, 2024

Sail offers types of the form {'tv1 'tv2 ... 'tvn, constraints. t}. In the refinement type literature those have been given many names (e.g. constrained type, predicate subtype, subtype, subset, refined sorts and refinement predicate). I have been calling them existential types, but that may be misleading as there is no clear duality with Sail's forall. What's the preferred term in the Sail community? Patrick Rondon, in his PhD work which introduced liquid types, calls them "refinement types", but liquid types don't encompass all of refinement types. I think we should standardise, especially as there is now a lot of work in this direction, including Liquid Haskell, and Liquid Rust.

I'm asking because having an agreed upon term is a first step towards better explaining how they work in Sail. We could even have a corresponding entry in https://alasdair.github.io/ (that I would be happy to write).

@Alasdair
Copy link
Collaborator

Alasdair commented Mar 15, 2024

I normally refer to them as existential types. They are basically the same thing as refinement types, but I think the syntax is a bit different than a typical presentation of refinement types, as they are stated as "there exists these type variables such that..." rather than as a refinement.

Note that {'tv1 'tv2 ... 'tvn, constraints. t} could have been exists 'tv1 'tv2 ... 'tvn, constraints. t, but existentials can appear in more places in the grammar than forall, so the lack of a closing delimiter was an issue. They also behave like existentials, so

val ex1 : forall 'n, 'n > 0. int('n) -> unit

val ex2 : {'n, 'n > 0. int('n)} -> unit

are the same, which corresponds to ∀x. (P(x) -> Q) <-> (∃x. P(x)) -> Q in logic.

@martinberger
Copy link
Author

val ex2 : {'n, 'n > 0. int('n)} -> unit

This should be val ex2 : {'n, 'n > 0. int('n)} I guess.

@martinberger
Copy link
Author

martinberger commented Mar 15, 2024

OK decision made, I will keep calling them existential types.

Sail programs would be infinitely more grep-able if there was a keyword exists rather than {...}. The problem of closing could be solved by having e.g. exists 'n, constraints.{...}, and symmetrically forall 'n, constraints.{...}

@Alasdair
Copy link
Collaborator

val ex2 : {'n, 'n > 0. int('n)} -> unit

This should be val ex2 : {'n, 'n > 0. int('n)} I guess.

The -> unit was intended. Internally an existential on the left-hand side of a function arrow will be canonicalised to the forall equivalent, but it does work like it should.

@martinberger
Copy link
Author

martinberger commented Mar 15, 2024

Internally an existential on the left-hand side of a function arrow will be canonicalised to the forall equivalent, but it does work like it should.

I'm not 100% sure I fully understand what that means. The function space constructor is contravariant in the first argument, so how does the semantics work?

@martinberger
Copy link
Author

martinberger commented Mar 15, 2024

Now that we are talking about the relationship of forall and exists in Sail, what are the constraints on nesting them? With higher-ranked types, you are quickly approaching the danger zone of undecidability.

Are there clear syntactic restrictions?

@Alasdair
Copy link
Collaborator

Alasdair commented Mar 15, 2024

There currently aren't any clear syntactic restrictions, other than that forall can only appear in function types. Existentials are allowed anywhere a type can appear. If nesting existentials would ever lead us to generate constraints during type checking that involve nested quantifiers that's when they are rejected.

If we had function subtyping, then {'n. int('n)} -> unit would be a subtype of {'n, 'n > 0. int('n)} -> unit, when {'n, '> 0. int('n)} is a subtype of {'n. int('n)} which is contravariance, but forall 'n. (int('n) -> unit) would also be subtype of forall 'n, 'n > 0. (int('n) -> unit) (brackets added for clarity), for the same reason, so it seems like the property of lifting an existential out of the left-hand side of a function arrow to a universal quantifier over the entire function arrow is orthogonal to contravariance?

@Timmmm
Copy link
Contributor

Timmmm commented Mar 15, 2024

Internally an existential on the left-hand side of a function arrow will be canonicalised to the forall equivalent, but it does work like it should.

Interesting! Does that mean forall is technically redundant?

@Alasdair
Copy link
Collaborator

Alasdair commented Mar 15, 2024

If you need to quantify over both an input type and an output type you still need it, forall 'n. int('n) -> int('n)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants