-
Notifications
You must be signed in to change notification settings - Fork 3k
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
EEP 70: Implement non-skipping generators (as an experimental feature) #8625
Conversation
CT Test Results 8 files 578 suites 2h 6m 2s ⏱️ For more details on these failures, see this check. Results for commit 46f1495. ♻️ 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 |
I see only one test failure here:
Doesn't seem to be related to my changes? |
Created an EEP about the proposed feature: erlang/eep#62 |
b18813d
to
5544be6
Compare
Updated with syntax proposed by @kikofernandez : |
I will look at this once the EEP is approved and we have some feedback from the community Thanks for your contribution |
Sorry for the delay. |
OTB has approved this PR (see this Erlang Forum post). Please remove the code defining this as a feature. Also remove the update of the primary bootstrap; we will take care of that. |
Thank you for the approval! I'll probably have time next week to make the requested changes. |
5544be6
to
11d97aa
Compare
I've done the requested changes:
|
I've now started to take a closer look at your code. When looking at the difference in the generated code for this branch (using https://github.com/bjorng/otp/tree/bjorn/use-strict-generators I noticed that changing
Some increase in code size is unavoidable, but I think this code can be improved. Consider this function:
Translating your Core Erlang code to its nearest equivalent Erlang code, it looks like this:
I suggest the following, which will result in shorter and simpler BEAM code than
|
Good catch! I rewrote the generated code to look like this on your example: s_fixed(Bin) ->
case Bin of
<<A:16,T/bitstring>> ->
[A|s_fixed(T)];
<<T/bitstring>> when T =/= <<>> ->
error({badmatch, T});
<<_/bitstring>> ->
[];
Other ->
error({bad_generator, Other});
end. It's a bit more verbose than
|
I don't see how the |
That test has been failing sporadically in our internal builds over the last 10 days. It's very likely a separate issue, not caused by your change. |
That's look much better. It will still create one unnecessary binary in the success case, but I'll accept it. @lucioleKi or I might revisit this in the future when this PR and the zip generators PR have been merged. I pushed a commit with a few nit-picky changes: breaking some lines, eliminating TABs on touched lines, and fixing broken indentation near touched lines. If you accept these changes, please squash the last four commits and force-push. (It seems that the first commit should remain a separate.) |
Currently existing generators are "relaxed": they ignore terms in the right-hand side expression that do not match the left-hand side pattern. Strict generators on the other hand fail with exception badmatch. The motivation for strict generators is that relaxed generators can hide the presence of unexpected elements in the input data of a comprehension. For example consider the below snippet: [{User, Email} || #{user := User, email := Email} <- all_users()] This list comprehension would filter out users that don't have an email address. This may be an issue if we suspect potentially incorrect input data, like in case all_users/0 would read the users from a JSON file. Therefore cautious code that would prefer crashing instead of silently filtering out incorrect input would have to use a more verbose map function: lists:map(fun(#{user := User, email := Email}) -> {User, Email} end, all_users()) Unlike the generator, the anonymous function would crash on a user without an email address. Strict generators would allow similar semantics in comprehensions too: [{User, Email} || #{user := User, email := Email} <:- all_users()] This generator would crash (with a badmatch error) if the pattern wouldn't match an element of the list. Syntactically strict generators use <:- (for lists and maps) and <:= (for binaries) instead of <- and <=. This syntax was chosen because <:- and <:= somewhat resemble the =:= operator that tests whether two terms match, and at the same time keep the operators short and easy to type. Having the two types of operators differ by a single character, `:`, also makes the operators easy to remember as "`:` means strict."
81293a6
to
46f1495
Compare
Thanks for the fixes! Squashed & pushed. |
First of all, this is a big PR and it's not 100% completed either. However, I'd like to get some feedback from you on how to proceed. I'll try to explain everything in this description.
What's in this PR?
maint
branch too, and if you're ok with them, I can create a separate PR for them. However, I also need these commits here to avoid merge conflicts with adjacent changes.[WIP]
labelled commit: I'm implementing a new language feature, non-skipping generators, for the comprehensions that corresponds to EEP 70, outline in Add proposal for non-skipping generators eep#62. I wanted to do this as an experimental feature, so it could be dropped if nobody else likes my idea (a quick informal survey across my colleagues showed that they would like to have it, but they are not a representative sample of the Erlang community), but I have questions about how experimental features are intended to work. More on that below../otp_build update_primary
(because I changed files instdlib
andcompiler
too). Here my question is whether I'm supposed to check in these compiled modules myself? I thought you may rather want to run this step in a trusted environment (like in CI) instead of on my laptop, but I'm not familiar with the process.What are non-skipping generators?
Currently existing generators are "skipping": they ignore terms in the right-hand side expression that do not match the left-hand side pattern. Non-skipping generators on the other hand fail with exception
badmatch
.The motivation for non-skipping generators is that skipping generators can hide the presence of unexpected elements in the input data of a comprehension. For example consider the below snippet:
This list comprehension would skip users that don't have an email address. This may be an issue if we suspect potentially incorrect input data, like in case
all_users/0
would read the users from a JSON file. Therefore cautious code that would prefer crashing instead of silently skipping incorrect input would have to use a more verbose map function:Unlike the generator, the anonymous function would crash on a user without an email address. Non-skipping generators would allow similar semantics in comprehensions too:
This generator would crash (with a
badmatch
error) if the pattern wouldn't match an element of the list.Syntactically non-skipping generators use
<-:-
(for lists and maps) and<=:=
(for binaries) instead of<-
and<=
. This syntax was chosen because<-:-
and<=:=
resemble the=:=
operator that tests whether two terms match. Since<-:-
and<=:=
were invalid syntax in previous versions of Erlang, they avoid backward compatibility issues.Nevertheless, I aim to add non-skipping generators as an experimental feature, that has to be explicitly enabled:
What is left to do?
While I've already declared the
non_skipping_generators
feature, in practice it doesn't work (the feature is never disabled), because I'm not sure how to best implement the feature check?The only feature Erlang had so far introduced a new keyword,
maybe
, and made it possible to enable/disable the feature based on this keyword. Namelyepp:parse_file/2
gained a newreserved_word_fun
option that allows customizing what unquoted atoms should be treated as keywords.erl_features
also assumes features may introduce new keywords and has API-s for dealing with them.However, the non-skipping generator features doesn't introduce any new features, but new operators:
<-:-
and<=:=
. Neithererl_features
norepp:parse_file/2
are prepared to deal with new operators unfortunately.I see a number of possible ways forward, but I'd need some guidance on which one would you prefer (or if you have a better idea):
erl_features
. On one hand this would make sense, since I believe features introducing new operators are not less likely to come than features introducing new keywords. However,erl_scan
currently implements a hand-rolled scanner (I guess for performance reasons) that would be quite hard to change to accept arbitrary new operators returned by theerl_features
module.erl_scan
the list of currently enabled features too. Then the logic about what operators to support per feature could be baked in to the hand-rolled scanner code.The issue of features and operators aside I'd need some feedback on whether I found all the places of the code base to update? I based my work on #5411 and the various PR-s related to the introduction of map comprehensions, but this is my first PR changing the language itself, so I may very well have missed something. And there are places, like the
erlang.el
Emacs code where I'm basically just shooting in the dark (I have no experience with writing Emacs Lisp at all).Finally, I verified that the tests I changed do pass, but haven't run the entire OTP test suite myself. I'll keep an eye on CI results of course!