-
Notifications
You must be signed in to change notification settings - Fork 68
Add EEP for partially applied functions #82
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
base: master
Are you sure you want to change the base?
Conversation
|
Would this also work? I didn't see an example. |
|
@essen yes, literals or variables as arguments are supported. I will clarify that in a later pass of the proposal once I collect all feedback. Thank you! |
|
Here is another alternative, that one can already try out in Erlang.
I just tried it out, the parse transfrom still works with the current Erlang. Here is how the examples would look like: 1> Fun = maps:get(username, _).
2> Fun(#{username => "Joe"}).
"Joe"Which is also equivalent to: 1> Fun = fun(X) -> maps:get(username, X) end.
2> Fun(#{username => "Joe"}).
"Joe"{some_config, some_mod:some_fun(_, answer, 42)}.
{some_config, fun(X) -> some_mod:some_fun(X, answer, 42) end}.hello(_, world, _)
fun(X, Y) -> hello(X, world, Y) endThis example does not work with cuts, it still needs to be wrapped in a fun manually. fun Mod:Fun(arg1, arg2, arg3)like this: fun() -> Mod:Fun(arg1, arg2, arg3) endThe example from the comment would look like this f() ->
Key = get_key(),
Fun = maps:get(Key, _),
io:format(user,"got: ~p~n", [Fun(#{k1 => 2})]).
get_key() -> k1.
1 > f().
got: 2 |
|
@albsch good call. Although Do you know if it restricts the arguments in any way? For example, can the arguments be complex expressions, such as |
|
Yes, runtime support for something like this would be great. Complex expressions are also allowed (by complex expression you mean the function application f() ->
Fun = maps:get(lists:flatten([]), _),
io:format(user,"got: ~p~n", [Fun(#{[] => 3})]).
1 > f().
got: 3
okIt's equivalent to (or rather, syntactically transformed into): Fun = fun(X) -> maps:get(lists:flatten([]), X) end |
|
How would Can an argument be an unbound variable? Can the fun name be a bound variable? F = foo,
fun F(_),
F(bar),or does it have to be a literal function name This would be allowed, right? foo(Y) -> Y-1.
bar(X) ->
F1 = fun Foo(X) -> X+1 end, % Arity 1
F2 = fun foo(X), % Arity 0
F3 = fun foo/1, % Arity 1
{F1(X), F2(), F3(X)}.
Maybe what I am getting at is that it is a bit subtle that F2 defines a function body (with a hidden header) where F1 and F3 defines a function header (and for F1 also a body). |
|
Thank you @albsch and @RaimoNiskanen!
I would say
Since
I agree they feel a bit too close. There are some trade-offs that could be made here:
Any thoughts? |
|
I think that it is tempting to be able to create an arity 0 fun for spawn: Parent = self(),
State = #{},
Pid = spawn(fun ?MODULE:server_loop(Parent, nolink, State)),
But |
Yes, I think this one wouldn't need runtime changes, since local functions are not external/serializable/persistent (we need a better way to describe those...).
Unfortunately I cannot speak about the implementation details. I assume one option is to extend the existing external function types to have a field that points to its partially applied arguments? For all existing |
|
I have updated the proposal with the feedback so far. @RaimoNiskanen, I have added a section on "Visual Cluttering", which includes your example and possible solutions. I included one additional solution, not mentioned above, which is to require partially applied functions to explicit list the arity too, hence If the version with arity is preferred, then the |
|
Specifying the arity is redundant, but maybe more readable. To me it associates more towards being a fun declaration instead of a function call. It is also clearer that it is a fun object of arity N that is created.
Isn't there a syntactical ambiguity in that |
|
Next up, an operator :) |
As per the current fun header definition: /* Fun objects.
*
* These have a special tag scheme to make the representation as compact as
* possible. For normal headers, we have:
*
* aaaaaaaaaaaaaaaa aaaaaaaaaatttt00 arity:26, tag:4
*
* Since the arity and number of free variables are both limited to 255, we can
* fit them both into the header word.
*
* 0000000keeeeeeee aaaaaaaa00010100 kind:1,environment:8,arity:8
*
* Note that the lowest byte contains only the function subtag, and the next
* byte after that contains only the arity. This lets us combine the type
* and/or arity check into a single comparison without masking, by using 8- or
* 16-bit operations on the header word. */
#define FUN_HEADER_ARITY_OFFS (_HEADER_ARITY_OFFS + 2)
#define FUN_HEADER_ENV_SIZE_OFFS (FUN_HEADER_ARITY_OFFS + 8)
#define FUN_HEADER_KIND_OFFS (FUN_HEADER_ENV_SIZE_OFFS + 8)
#define MAKE_FUN_HEADER(Arity, NumFree, External) \
(ASSERT((!(External)) || ((NumFree) == 0)), \
(_TAG_HEADER_FUN | \
(((Arity)) << FUN_HEADER_ARITY_OFFS) | \
(((NumFree)) << FUN_HEADER_ENV_SIZE_OFFS) | \
((!!(External)) << FUN_HEADER_KIND_OFFS)))There's a few places that assume that external funs don't have an environment at the moment, notably in the external term format, but otherwise it would be easy to let them have an environment too. I think it's possible to make these terms round-trip to old nodes and back without loss, by using |
|
@jhogberg one thing of note is that this new environment maps arguments to positions. They are still ordered, but they can have gaps, such as |
eeps/eep-00XX.md
Outdated
| nodes nor be persisted to disk, so when dealing with distribution, | ||
| disk persistence, or hot code upgrades, you must carefully stick with | ||
| MFArgs. Similarly, configuration files do not support anonymous | ||
| functions, and MFArgs are the main option. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing an anonymous function with an environment over the distribution works. I must be misunderstanding something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Poor phrasing on my end, I should clarify it only works if you have the exact same module version (which, unless you declare a version, will change even with minor changes to the source). I will improve that on a future pass to the EEP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks. Yes, I think the versioning issue should be mentioned.
|
We've discussed this internally, and though we didn't reach a final decision, we liked the proposal and agreed on the following:
|
Not by much, the implementations I had in mind are relatively easy to implement with reasonable performance. |
|
Thanks everyone, I have applied the latest round of feedback, including the ones from @jhogberg.
I decided to include
I kept that section in for discussion, it can be removed in future revisions. |
eeps/eep-00XX.md
Outdated
| nodes nor be persisted to disk if they preserve the same module version. | ||
| Therefore, when dealing with distribution, disk persistence, or hot code | ||
| upgrades, it is preferrable to use MFArgs instead. Similarly, configuration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested details change:
nodes or be persisted to disk if it is the same module version on both ends.
Therefore, when dealing with distribution, disk persistence, or hot code
upgrades, it is preferrable/essential to use MFArgs instead. Similarly, configuration
Maria-12648430
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some typos, some questions/suggestions/opinions :)
| * Only allow remote partially applied functions, so `fun foo(_, ok)` | ||
| is invalid, but `fun some_mod:foo(_, ok)` is accepted. Unfortunately, | ||
| this may lead to developers doing external calls when a local call | ||
| would suffice; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not look practical to me. Or useful. Or a good way. Or whatever 🤷♀️
| * Require all partially applied functions to have at least one `_`, | ||
| forbidding `fun foo(X)` or `fun some_mod:some_fun(Args)`. This does | ||
| add a syntactical annoyance but it does not remove any capability | ||
| as any function without placeholder can be written as a zero-arity | ||
| function; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like that one. The restriction would read like "... unless your partial application results in full application", which IMO is a stumbling block that at least I wouldn't want.
| The lack of a prefix makes it harder to spot when a function is created | ||
| and also leads to visual ambiguity, such as in the code below: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... which kind of drives home my point made re dropping or keeping the fun
Co-authored-by: Maria Scott <67057258+Maria-12648430@users.noreply.github.com>
No description provided.