Skip to content

Spec: Rewrite the type system fundamentals. #17447

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

Merged
merged 7 commits into from
Jun 7, 2023

Conversation

sjrd
Copy link
Member

@sjrd sjrd commented May 9, 2023

There are two fundamental differences between the type sytems of Scala 2 and 3, which made a fundamental rewrite desirable:

  • Scala 2 defines path-dependent types in terms of type projections, whereas Scala 3 is path-dependent first, and has not general type projects.
  • Scala 2 has ad hoc higher-kinded type parameters and polymorphic type aliases, whereas Scala 3 has first-class type lambdas and is fundamentally higher kinded.

We therefore rewrite the entire type system section of the spec to be higher-kinded and path-dependent first. In the process, we also revise refined types to better fit the path-dependent nature.

I (sjrd) took the liberty to add myself as a contributor in the index page of the spec, as I believe this constitutes a fundamental contribution to the language specification.


The rendered version of the Types chapter can be seen here:
Types Scala 3.pdf (old version)
Types Scala 3.pdf

@sjrd sjrd requested a review from odersky May 9, 2023 16:09
sjrd added 5 commits May 30, 2023 11:16
There are two fundamental differences between the type sytems of
Scala 2 and 3, which made a fundamental rewrite desirable:

- Scala 2 defines path-dependent types in terms of type projections,
  whereas Scala 3 is path-dependent first, and has not general
  type projects.
- Scala 2 has ad hod higher-kinded type parameters and polymorphic
  type aliases, whereas Scala 3 has first-class type lambdas and
  is fundamentally higher order.

We therefore rewrite the entire type system section of the spec
to be higher-kinded and path-dependent first. In the process, we
also revise refined types to better fit the path-dependent nature.

I (sjrd) took the liberty to add myself as a contributor in the
index page of the spec, as I believe this constitutes a fundamental
contribution to the language specification.
And better define what a "type definition" is.
To align with the rest of the spec, which is syntax-first.
@sjrd sjrd force-pushed the spec-type-system-fundamentals branch from d7ea469 to 6021b7b Compare May 30, 2023 09:58
@sjrd
Copy link
Member Author

sjrd commented May 30, 2023

@odersky I have updated the spec according to what we discussed last week. In particular, its starts with the concrete syntax of types now, and the desugaring of type lambdas and refined types is more explicit.

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the new structure. It's a great improvement to what we had before.

## Paths
In this specification, we specify and use types in terms of the following _abstract syntax_.
The abstract syntax does not directly correspond to concrete syntax.
It abstracts away irrelevant details such as precedence and grouping, and contains shapes of types that cannot be directly expressed using the concrete syntax.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Mark shapes that cannot be expressed in syntax specially (maybe in Italics? or else in a comment at the line's end.)


### Type Constructors

To each type constructor corresponds an _inferred type parameter clause_ which is computed as follow:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
To each type constructor corresponds an _inferred type parameter clause_ which is computed as follow:
To each type constructor corresponds an _inferred type parameter clause_ which is computed as follows:

```ebnf
Type ::= FunctionArgTypes ‘=>’ Type
FunctionArgTypes ::= InfixType
| ‘(’ [ ParamType {‘,’ ParamType } ] ‘)’
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependent function types are missing here. The reference syntax is actually:

FunTypeArgs       ::=  InfixType
                    |  ‘(’ [ FunArgTypes ] ‘)’
                    |  FunParamClause
FunParamClause    ::=  ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’
TypedFunParam     ::=  id ‘:’ Type

| TypeLambdaParams '=>' Type
FunctionArgTypes ::= InfixType
| ‘(’ [ FunArgTypes ] ‘)’
| FunParamClause
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FunParamClause is not further explained. We need to explain that , e.g.

  (x: T) => x.M

expands to

 Function1[T, U] { def apply(x: T): x.M }

assuming that U is the least supertype of x.M that does not mention x.

Aside: This encoding might change. We'd really like it to be something like

 DependentFunction1 { def apply(x: T): x.M }

instead.

SimpleType ::= Path ‘.’ ‘type’
Type ::= FunctionArgTypes ‘=>’ Type
FunctionArgTypes ::= InfixType
| ‘(’ [ ParamType {‘,’ ParamType } ] ‘)’
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax summary gives a different expansion for FunctionArgTypes. These need to be aligned.

- Otherwise, if either ´M_1´ or ´M_2´ is a class result, the result is that one.
- Otherwise, ´M_1´ and ´M_2´ must either both be term results or both be type results.
- If they are term results with underlying types ´U_1´ and ´U_2´ and stable flags ´s_1´ and ´s_2´, the result is a term result whose underlying type is `meet(´U_1´, ´U_2´)` and whose stable flag is ´s_1 \lor s_2´.
- If they are type results with underlying type definitions ´D_1´ and ´D_2´, the result is a type result whose underlying type definition is `intersect(´D_1´, ´D_2´)`.

## Relations between types
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Equivalence meaning "interchangeable in all contexts" is prone to mislead. Also because of its triple equals sign. It's definitely not structural equivalence. So in the table below I would put conformance first, and then define equivalence as mutual conformance. Also, if possible use another symbol like =:= or and = with a ~ on top.

- ´\sigma (>: M_i <: G_i)´ is contained in ´>: L_i <: H_i´ (i.e., ´L_i <: \sigma M_i´ and ´\sigma G_i <: H_i´).
- ´S = p.X´ and `´T = [\pm b_1 >: M_1 <: G_1, ..., \pm b_n >: M_n <: G_n]´ =>> ´T_1´` and ´S´ is a type constructor with ´n´ type parameters and:
- `´([\pm a_1 >: L_1 <: H_1, ..., \pm a_n >: L_n <: H_n]´ =>> ´S[a_1, ..., a_n]) <: T´` where the ´a_i´ are copies of the type parameters of ´S´ (i.e., we can eta-expand ´S´ to compare it to a type lambda).
- `´T = T_1´ { ´R´ }` and ´S <: T´ and ´S´ is a stable type ´p´ (TODO: what if it is not stable?) and:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we solve that by skolemization. What happens is that we compute the member type of R in T_1. That goes through an asSeenFrom. The asSeenFrom will skolemize when we have to replace a C.this type with a non-stable prefix. So maybe we should add that case to the definition of asSeenFrom.

Compare with the treatment of QualSkolemType in TypeOps.asSeenFrom.

- ´p´ is ´S´ if ´S´ is a stable type and ´∃ \alpha : S´ otherwise, and
- ´p'´ is the result of replacing any top-level recursive type `{ ´\gamma´ => ´Z´ }` in ´p´ with ´[\gamma := p]Z´ (TODO specify this better).
- `´S = (´=> ´S_1)´` and `´T = (´=> ´T_1)´` and ´S_1 <: T_1´.
- `´S =´ scala.Null` and:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to also say what happens under -YexplicitNulls here.


We define `matches(´S´, ´T´)` where ´S´ and ´T´ are types or methodic types as:

<!-- TODO We should mention parameter substitution here. -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should do that.

- A type alias `type ´t´[´T_1´, ..., ´T_n´] = ´T´` subsumes a type alias `type ´t´[´T_1´, ..., ´T_n´] = ´T'´` if ´T \equiv T'´.
- A type declaration `type ´t´[´T_1´, ..., ´T_n´] >: ´L´ <: ´U´` subsumes a type declaration `type ´t´[´T_1´, ..., ´T_n´] >: ´L'´ <: ´U'´` if ´L' <: L´ and ´U <: U'´.
- A type or class definition that binds a type name ´t´ subsumes an abstract type declaration `type t[´T_1´, ..., ´T_n´] >: L <: U` if ´L <: t <: U´.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further below: I think we should drop the "Weak Conformance" section 3.6.3. Then in 3.6.4 we should refer to numeric widening as an implicit conversion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The section on Volatile types should be dropped. It is replaced by a notion of realizability.

The section on Erasure should be updated to the new rules.

Generally, all usages of compound types (using with) should be dropped.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weak conformance will be the subject of a separate PR that introduces harmonization, so I would rather not touch it now.

I updated erasure. For the volatile types I'll have to investigate how realizability affects that section.

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the new structure. It's a great improvement to what we had before.

@odersky odersky assigned sjrd and unassigned odersky Jun 6, 2023
@sjrd
Copy link
Member Author

sjrd commented Jun 6, 2023

I think I have addressed everything so far except the Volatile Types -> Realizable comment.

@sjrd
Copy link
Member Author

sjrd commented Jun 7, 2023

Volatility now replaced by realizability as well. (also updated the PDF)

@sjrd sjrd requested a review from odersky June 7, 2023 08:32
@sjrd sjrd assigned odersky and unassigned sjrd Jun 7, 2023
Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks all good now.

@odersky odersky assigned sjrd and unassigned odersky Jun 7, 2023
@sjrd sjrd merged commit 482dfeb into scala:main Jun 7, 2023
@sjrd sjrd deleted the spec-type-system-fundamentals branch June 7, 2023 12:35
Kordyjan added a commit that referenced this pull request Nov 23, 2023
[Cherry-picked 8e65a2c]
[Cherry-picked 978d4e4]
[Cherry-picked 6f21d93]
[Cherry-picked 8657620]
[Cherry-picked 6021b7b]
[Cherry-picked a99f0b6]
[Cherry-picked 254f23e]
Kordyjan added a commit that referenced this pull request Nov 29, 2023
[Cherry-picked 8e65a2c]
[Cherry-picked 978d4e4]
[Cherry-picked 6f21d93]
[Cherry-picked 8657620]
[Cherry-picked 6021b7b]
[Cherry-picked a99f0b6]
[Cherry-picked 254f23e]
Kordyjan added a commit that referenced this pull request Dec 8, 2023
Backports #17447 to the LTS branch.

This PR was initially erroneously skipped.
It also includes changes form #17940.

[skip ci]
@Kordyjan Kordyjan added this to the 3.3.2 milestone Dec 14, 2023
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

Successfully merging this pull request may close these issues.

3 participants