otp icon indicating copy to clipboard operation
otp copied to clipboard

Make assignment (match) in comprehension work as strict generator

Open richcarl opened this issue 1 year ago • 7 comments

Implements EEP 77 (https://github.com/erlang/eep/pull/74)

It would be useful to be able to easily bind variables in the qualifiers of a comprehension, for example:

[Z || X <- Ls,
      {foo, Y} = g(X),
      Z <- f(Y, h(Y))].

You can do this today by writing a singleton generator:

[Z || X <- Ls,
      {foo, Y} <- [g(X)],  % produce single element
      Z <- f(Y, h(Y))].

but this has some drawbacks: the intent is not clear to the reader; you have to remember to write a single element list around the right hand side argument (which itself could be a list); you probably want it to be a strict generator so typos don't just silently yield no elements; and someone might edit the code later and accidentally add extra elements to the right hand side, causing unintended Cartesian combinations.

It is in fact already allowed syntactically to have a = match expression in the qualifiers, but this is just interpreted as any other expression - thus expected to produce a boolean value, and if false, the current element will be skipped. Hence, any qualifier to the right of a V = Expr match will only execute if V is true. We can therefore expect that no such uses exist in practice. (The OTP code base has been checked and does not contain any.) To be completely safe, matches in a comprehension qualifier position can first be made an error, and then allowed with these binding semantics in the following major release.

This is related but orthogonal to #9134. It would make those few cases where variables have been exported from subexpressions of qualifiers much nicer to rewrite than requiring them to be singleton generators.

richcarl avatar Dec 07 '24 18:12 richcarl

CT Test Results

     7 files     573 suites   2h 0m 38s ⏱️  4 475 tests  4 347 ✅ 128 💤 0 ❌ 10 668 runs  10 512 ✅ 156 💤 0 ❌

Results for commit 2480ad70.

:recycle: 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

github-actions[bot] avatar Dec 07 '24 18:12 github-actions[bot]

Correction: 1 occurence in the OTP test suites, in singleton_inference.erl, which is weird enough that I won't even try to rewrite it myself. Maybe @jhogberg who wrote it (commit ebc461e182) can help?

richcarl avatar Dec 07 '24 19:12 richcarl

Turned into a feature. Becomes an error if it occurs when the feature is not enabled.

Also fixed multiple problems with the features implementation.

richcarl avatar Jan 30 '25 16:01 richcarl

Rebased on #10376 to make the Features fix a separate thing.

richcarl avatar Nov 16 '25 09:11 richcarl

Rebased again and updated feature introduced version to 29.

richcarl avatar Nov 21 '25 21:11 richcarl

OTB accepted the EEP, but asks for some editorial updates of the EEP:

  • Discussion of compatibility should be under its own heading in the EEP.
  • Please add some more examples, showing more realistic use cases. The existing example is very complicated. It would be better to have a few simpler examples.
  • Clarify whether using an assigned variable can be used on the left-hand side of the comprehension and give an example of what happens.

Regarding the implementation, I couldn't get it to work. All I got was:

t.erl:9:6: matches using '=' are not allowed in comprehension qualifiers
unless the experimental 'compr_assign' language feature is enabled.
With 'compr_assign' enabled, a match 'P = E' will behave as a
strict generator 'P <-:- [E]'."
%    9|      {foo, Y} = id({foo, X}),
%     |      ^

I tried three ways to enable the feature.

bjorng avatar Dec 11 '25 13:12 bjorng

I tried three ways to enable the feature.

Fixed. I also made some clarifications (comments and a renamed internal function) to how the state of features during parsing/compilation works.

richcarl avatar Dec 12 '25 12:12 richcarl

Thanks, I've manually confirmed that the example can be compiled.

I can't see that you have added any new test cases to actually test assignments in comprehensions. I suggest that you enable the feature in the existing lc_SUITE and add some tests.

Does erl_eval happen to just work or does it need to be updated? In any case, there should be a test case in erl_eval_SUITE.

bjorng avatar Dec 19 '25 08:12 bjorng