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

Adding documentation for subtyping #1990

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
35ff633
Add subsumption rule between Record/Dictionary
Eckaos Jun 25, 2024
abbbe1f
Remove commented code
Eckaos Jun 25, 2024
00a9537
Remove clone in pattern match, replaced fold by a for loop, take into…
Eckaos Jun 26, 2024
c99634e
Update & add comments
Eckaos Jun 27, 2024
bb20fc9
Add integration tests
Eckaos Jun 27, 2024
9e0f0e4
add test with chaining std function on records
Eckaos Jul 3, 2024
f6cd3ab
add manual documentation for record/dictionary subtyping
Eckaos Jul 4, 2024
bfeb094
Merge branch 'tweag:master' into subtyping/Record_Dictionary_Document…
Eckaos Jul 6, 2024
6bb2e6a
Merge branch 'tweag:master' into subtyping/Record_Dictionary_Document…
Eckaos Jul 8, 2024
871a0bf
Modify manual
Eckaos Jul 17, 2024
65ed749
Changing contract to typing
Eckaos Jul 17, 2024
af81a9f
Update core/tests/integration/inputs/typecheck/chaining_dictionary_fu…
Eckaos Jul 18, 2024
a71465e
Update doc/manual/typing.md
Eckaos Jul 18, 2024
a95489e
Fix test
Eckaos Jul 18, 2024
4c8b493
Add motivation
Eckaos Jul 19, 2024
9709082
Merge branch 'tweag:master' into subtyping/Record_Dictionary_Document…
Eckaos Jul 19, 2024
2d7a14b
Fix code example
Eckaos Jul 19, 2024
759eafb
Add Array/Array and Dictionary/Dictionary subtyping documentation
Eckaos Jul 22, 2024
d0b1a1e
Fix code (does not fix TypecheckError)
Eckaos Jul 22, 2024
c22f2a1
Fix code examples (does fix errors)
Eckaos Jul 23, 2024
292e84f
Fix subsumption
Eckaos Jul 23, 2024
3bb123b
Fix subsumption
Eckaos Jul 24, 2024
b9db772
Improve the manual section on subtyping
yannham Jul 24, 2024
d824ff0
Add generic example to work around function subsumption
Eckaos Jul 25, 2024
d94da2b
Update doc/manual/typing.md
yannham Jul 26, 2024
e1f837e
Update doc/manual/typing.md
yannham Jul 26, 2024
107d6a5
Fix format problem
Eckaos Jul 26, 2024
77773a9
Update doc/manual/typing.md
yannham Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions core/benches/nixpkgs/lists.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,8 @@
@ (toporest | { result : Array Dyn }).result
} : { _ : Array Dyn },

#TODO: can it be statically typed?
sort
| forall a. (a -> a -> Bool) -> Array a -> Array a
: forall a. (a -> a -> Bool) -> Array a -> Array a
| doc m%"
Sort a list based on a comparator function which compares two
elements and returns true if the first argument is strictly below
Expand Down
1 change: 1 addition & 0 deletions core/src/typecheck/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,7 @@ pub fn subsumption(
checked: UnifType,
) -> Result<(), UnifError> {
let inferred_inst = instantiate_foralls(state, &mut ctxt, inferred, ForallInst::UnifVar);
let checked = checked.into_root(state.table);
match (inferred_inst, checked) {
(
UnifType::Concrete {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# test.type = 'error'
# [test.metadata]
# error = 'EvalError::BlameError'
let x = {foo = 1, bar = 2} in
let y = std.record.insert "baz" 5 x in
let _ = std.record.get "foo" y in
let z = std.record.remove "foo" y in
std.record.get "foo" z
84 changes: 84 additions & 0 deletions doc/manual/typing.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,90 @@ The following type constructors are available:
}
```

### Subtyping

While distinct types are usually incompatible, some types might actually be
safely converted to some other types. For example, `{foo = 5}` is inferred to
have type `{foo : Number}` by default, but is also a valid value of type `{_ :
Number}`. The latter type is less precise, but might be required for dynamic
dictionary operations such as `std.record.insert`:

```nickel
let extended : { _ : Number } =
let initial : { foo : Number } = { foo = 5 } in
std.record.insert "bar" 5 initial in
extended
```

In this example, there is a silent conversion from `{foo : Number}` to `{_ :
Number}`. This is safe because `foo` is of type `Number`, and it's the only
field, which means that a value of type `{foo : Number}` is effectively
dictionary of numbers. In the typing jargon, `{foo : Number}` is said to be a
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
dictionary of numbers. In the typing jargon, `{foo : Number}` is said to be a
a dictionary of numbers. In the typing jargon, `{foo : Number}` is said to be a

*subtype* of `{_ : Number}`. We will write `T <: U` to say that `T` is a subtype
of `U`: whenever a value of type `U` is expected, we can use a value of type `T`
as well.

Currently, Nickel supports the following subtyping rule which generalizes the
example above:

- **Record/Dictionary** subtyping :
`{ field1 : T1, ..., fieldn : Tn } <: { _ : T }` if for each `i`, `Ti <: T`
That is, a record type is a subtype of a dictionary type if the type of each
field is a subtype of the type of dictionary elements.

Example:

```nickel
let occurrences : {a : Number, b : Number, c : Number} = {a = 1, b = 3, c = 0} in
std.record.map (fun char count => count + 1) occurrences : {_ : Number}
```

Subtyping extends to type constructors in the following way:

- **Equality** : a type is trivially a subtype of itself, that is `T <: T`.
- **Array**: `Array T <: Array U` if `T <: U`.

Example:

```nickel
let block : _ =
let array_of_records : Array {a : Number} = [{a = 5}] in
let inject_b : Array {_ : Number} -> Array {_ : Number} =
std.array.map (fun a => std.record.insert "b" 5 a)
in

inject_b array_of_records
in block
```

Here, `Array {a : Number}` is accepted where `Array {_ : Number}` is expected,
because `{a : Number} <: { _ : Number }`.
- **Dictionary**: similarly, `{ _ : T } <: { _ : U }` if `T <: U`.

Example:

```nickel
let block : _ =
let dict_of_records : { _ : {a : Number}} = {r = {a = 5}} in
let inject_b : { _ : {_ : Number}} -> { _ : {_ : Number}}
= std.record.map (fun _ x => std.record.insert "b" 5 x)
in

inject_b dict_of_records in
block
```

Here, `{_ : {a : Number}}` is accepted where `{_ : {_ : Number}}` is expected,
because `{a : Number} <: { _ : Number }`.

**Remark**: if you've used languages with subtyping before, you might expect the
presence of a rule for function types, namely that `T -> U <: S -> V` if `S <:
T` and `U <: V`. This is not the case in Nickel, for various technical reasons.
However, you can work around this limitation by expanding a given function:
If `f` has type `T -> U`, but a value of type `S -> V` is required,
use `fun x => f x` instead, which has the same runtime behavior
but isn't typed in the same way.

### Polymorphism

#### Type polymorphism
Expand Down