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

Make tuple calls opt-in #1499

Merged
merged 1 commit into from
Jul 6, 2017
Merged

Make tuple calls opt-in #1499

merged 1 commit into from
Jul 6, 2017

Conversation

josevalim
Copy link
Contributor

Tuple calls is the ability to invoke a function on a tuple
as first argument:

1> Var = dict:new().
{dict,0,16,16,8,80,48,
      {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
      {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}
2> Var:size().
0

This behaviour is considered by most to be undesired and confusing,
especially when it comes to errors. For example, imagine you invoke
"Mod:new()" where a Mod is an atom and you accidentally pass {ok, dict}.
It raises:

{undef,[{ok,new,[{ok,dict}],[]},...]}

As it attempts to invoke ok:new/1, which is really hard to debug
as there is no call to new/1 on the source code.

Furthemore, this behaviour is implemented at the VM level, which
imposes such semantics on all languages running on BEAM.

Since we cannot remove the behaviour above, this proposal makes the
behaviour opt-in with a compiler flag:

-compile(tuple_calls).

This means that, if a codebase relies on this functionality, they
can keep compatibility by configuring their build tool to
always use the 'tuple_calls' flag or explicitly on each module.

As long as the compile attribute above is listed, the codebase will
work on old and new Erlang versions alike. The only downside of the
current implementation is that modules compiled on OTP 20 that rely
on 'tuple_calls' will have to be recompiled to run with 'tuple_calls'
on OTP 21+.

stdlib, emulator and compiler test suites have been tested and
successfully passed after this change.

@OvermindDL1
Copy link

Does this make it so you put -compile(tuple_calls). on the module where you perform the tuple calls or do you put -compile(tuple_calls). on the module that you can perform tuple calls on?

I'd prefer the latter personally as then if someone tries to tuple-call {ok, Blah} it would give whatever error you are giving with this PR (cannot call a tuple or whatever) but if they call it on a module that allows tuple calls on it then it would work properly, all without the user needing to worry or care about what they add in. When loading 'some' module in the system it can see the flag on it and add it's name/atom to some global list that can be checked in the call function for fairly quick access.

@josevalim
Copy link
Contributor Author

josevalim commented Jul 3, 2017

@OvermindDL1 definitely the former for the reasons you said: the latter would require a global lookup table (adding complexity) and it would make dispatch slower than it is today. If you would like the second approach, you can implement it using a core_transform or erlang_transform with a global lookup (such as ETS).

@OvermindDL1
Copy link

Considering the number of tuple modules in general would be low (and even if not a good set implementation in C would be fast enough regardless), a global set in C should be able to perform it fast enough for about any case that it would be useful in, without requiring any transforms and would work among any BEAM language.

@bjorng
Copy link
Contributor

bjorng commented Jul 4, 2017

I like the general idea, but I have a few questions.

In the v3_core you have started to use the process dictionary. We generally try avoid using the process dictionary unless there is a very good reason. Do you have a very good reason?

Have you tested that the parse transform for parameterized modules?

https://github.com/erlang/pmod_transform

@bjorng bjorng self-assigned this Jul 4, 2017
@bjorng bjorng added feature team:VM Assigned to OTP team VM labels Jul 4, 2017
@josevalim
Copy link
Contributor Author

In the v3_core you have started to use the process dictionary. We generally try avoid using the process dictionary unless there is a very good reason. Do you have a very good reason?

The reason is to avoid traversing the options list on every tuple/cons, similar to what is done in sys_core_fold. I am not sure if it is a very good reason though. Let me know if this change should be reverted.

Have you tested that the parse transform for parameterized modules?

Not yet. Do you believe it should automatically add the -compile attribute added by this PR?

@bjorng
Copy link
Contributor

bjorng commented Jul 4, 2017

The reason is to avoid traversing the options list on every tuple/cons, similar to what is done in sys_core_fold.

sys_core_fold uses the process dictionary for historic reasons, not for performance reasons.

I only see that you do get(core_tuple_calls) when processing #icall{} (external call), and if you would have used andalso instead of and, it would only be called when the the module is not an atom. So I do think you should revert that change. (If you for any reason would need a boolean instead of the compiler options, you can put it in the #core{} record.)

Do you believe it should automatically add the -compile attribute added by this PR?

Yes. That way all calls from modules that use the parse transform would continue to work (after re-compilation).

Tuple calls is the ability to invoke a function on a tuple
as first argument:

    1> Var = dict:new().
    {dict,0,16,16,8,80,48,
          {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
          {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}
    2> Var:size().
    0

This behaviour is considered by most to be undesired and confusing,
especially when it comes to errors. For example, imagine you invoke
"Mod:new()" where a Mod is an atom and you accidentally pass {ok, dict}.
It raises:

    {undef,[{ok,new,[{ok,dict}],[]},...]}

As it attempts to invoke ok:new/1, which is really hard to debug
as there is no call to new/1 on the source code.

Furthemore, this behaviour is implemented at the VM level, which
imposes such semantics on all languages running on BEAM.

Since we cannot remove the behaviour above, this proposal makes the
behaviour opt-in with a compiler flag:

    -compile(tuple_calls).

This means that, if a codebase relies on this functionality, they
can keep compatibility by adding configuring their build tool to
always use the 'tuple_calls' flag or explicitly on each module.

As long as the compile attribute above is listed, the codebase will
work on old and new Erlang versions alike. The only downside of the
current implementation is that modules compiled on OTP 20 that rely
on 'tuple_calls' will have to be recompiled to run with 'tuple_calls'
on OTP 21+.
@josevalim
Copy link
Contributor Author

sys_core_fold uses the process dictionary for historic reasons, not for performance reasons.

Thank you for this perspective. I have undone those changes.

Have you tested that the parse transform for parameterized modules?

I have sent a pull request and I have verified that pmod_transform works under my branch after the PR is applied: erlang/pmod_transform#4

@bjorng bjorng added the testing currently being tested, tag is used by OTP internal CI label Jul 4, 2017
@bjorng bjorng merged commit 250a10f into erlang:master Jul 6, 2017
@josevalim josevalim deleted the jv-tuple-calls branch March 16, 2018 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature team:VM Assigned to OTP team VM testing currently being tested, tag is used by OTP internal CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants