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

Update and uniformise lists specs #8736

Open
wants to merge 3 commits into
base: maint
Choose a base branch
from

Conversation

VLanvin
Copy link

@VLanvin VLanvin commented Aug 20, 2024

Uniformise specs to adhere to the proposed EEP 71.

Many specs use when constraints and :: inconsistenly, making it hard for type-checkers to reason about.

For example,

-spec reverse(List1) -> List2 when
      List1 :: [T],
      List2 :: [T],
      T :: term().

in this spec, List1 and List2 are arguably defined as aliases for [T], while T is defined to be "a subtype of" term().

Removing the last constraint makes it clearer that the returned list has the same type as the input list:

-spec reverse(List1) -> List2 when
      List1 :: [T],
      List2 :: [T].

This PR unifies all specs of the lists module under this principle, while minimising the amount of changes to specs: it keeps the spirit of using aliases and when constraints instead of inlining types.

Uniformise specs to adhere to the proposed
[EEP 71](erlang/eep#63).

Many specs use `when` constraints and `::` inconsistenly, making
it hard for type-checkers to reason about.

For example,
```
-spec reverse(List1) -> List2 when
      List1 :: [T],
      List2 :: [T],
      T :: term().
```
in this spec, `List1` and `List2` are arguably defined as aliases
for `[T]`, while `T` is defined to be "a subtype of" `term()`.

Removing the last constraint makes it clearer that the returned
list has the same type as the input list:
```
-spec reverse(List1) -> List2 when
      List1 :: [T],
      List2 :: [T].
```

This PR unifies all specs of the lists module under this principle,
while minimising the amount of changes to specs: it keeps the spirit
of using aliases and `when` constraints instead of inlining types.
@CLAassistant
Copy link

CLAassistant commented Aug 20, 2024

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

github-actions bot commented Aug 20, 2024

CT Test Results

    2 files     95 suites   56m 48s ⏱️
2 153 tests 2 105 ✅ 48 💤 0 ❌
2 512 runs  2 462 ✅ 50 💤 0 ❌

Results for commit 50776e6.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@kikofernandez kikofernandez self-assigned this Aug 20, 2024
lib/stdlib/src/lists.erl Outdated Show resolved Hide resolved
lib/stdlib/src/lists.erl Show resolved Hide resolved
lib/stdlib/src/lists.erl Show resolved Hide resolved
lib/stdlib/src/lists.erl Show resolved Hide resolved
@kikofernandez kikofernandez added team:VM Assigned to OTP team VM enhancement types The issue is related to types labels Aug 20, 2024
- Remove recursive constraints in DeepLists to adhere to EEP 71
- Rename variables in docs according to specs
@@ -102,8 +102,7 @@ equal to `Key`. Returns `Tuple` if such a tuple is found, otherwise `false`.
-spec keyfind(Key, N, TupleList) -> Tuple | false when
Key :: term(),
N :: pos_integer(),
TupleList :: [Tuple],
Tuple :: tuple().
TupleList :: [Tuple].
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't Kiko's comment apply to this too? (And throughout).
If you're not going to demand tuples (technically correct, but really confusing), then don't call it a TupleList

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, what is this really saying... "the result Tuple must have been part of the original list"? Kind of odd (borderline unhelpful?) losing track of that the result will always be a tuple.

Copy link
Author

Choose a reason for hiding this comment

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

I've updated the aliases and the corresponding docs to remove the mention of TupleList, since it is not actually required to be all tuples.

Yes this states that the result is part of the original list, which IMO conveys more information than just saying it is a tuple().
If your input is [{atom(), atom()}] then your output will be {atom(), atom()}, which is more precise than just tuple().

lib/stdlib/src/lists.erl Outdated Show resolved Hide resolved
- Clarify in docs and aliases functions for which not all
elements have to be tuples, e.g., `lists:keyfind`. Aliases
for lists are renamed from `TupleList` to `List`, and elements
from `Tuple` to `Elem`.
- Revert changes to specs for functions that actually need a
list of tuples, such as `lists:keymerge` and `lists:keysort`
variants. Enforce `[tuple()]` arguments for these functions.
@garazdawi
Copy link
Contributor

In order to make sure that the implementation and the specs match I think that for any spec where you change the argument types you should write new testcases that verify that this is true. If such a testcase already exists then add a comment here on github pointing to the test that verifies it.

Comment on lines -102 to +105
-spec keyfind(Key, N, TupleList) -> Tuple | false when
-spec keyfind(Key, N, List) -> Elem | false when
Key :: term(),
N :: pos_integer(),
TupleList :: [Tuple],
Tuple :: tuple().
List :: [Elem].
Copy link
Contributor

Choose a reason for hiding this comment

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

In the current state of the type system, we lack bounded quantification. If we were to accept this type as written, our users will lose type information because not all type systems can infer that Elem must be a subtype of tuple(), more specifically, of the tuple passed as argument in the list.

I understand that is the more precise one can get, but it also means losing type information.
If we add bounded quantification, we could say:

-spec keyfind(Key, N, List) -> Elem | false when
    Key :: term(),
    N :: pos_integer(),
    List :: [Elem],
    Elem <: tuple().

In this case, some type systems may choose Elem = tuple() and move on, and strong type systems may figure out the exact tuple value expected. For now, I do not think we can go with this level of generic programming

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement team:VM Assigned to OTP team VM types The issue is related to types
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants