Add EEP for partially applied functions#82
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 |
| ```erlang | ||
| foo(Y) -> Y-1. | ||
| bar(X) -> | ||
| F1 = fun Foo(X) -> X+1 end, % Arity 1 |
There was a problem hiding this comment.
Foo starting with a capital is it a typo? or I'm missing something?
There was a problem hiding this comment.
It's the (unused) name of the local function bound to F1.
There was a problem hiding this comment.
Yes. That name is only valid within the fun Foo. It is typically used for recursion from within the fun.
There was a problem hiding this comment.
I was misguided by foo repetition.
But does it matter in this context if its a named function or anonymous?
There was a problem hiding this comment.
I intentionally misguided by showing that the only difference between fun Foo(X)... and fun foo(X)... is the capital letter, and where foo(X) is a fun body (or reference, or call, to a named function), and Foo(X) is a fun header (for an anonymous fun). What tells them apart is what follows, that is an arrow -> vs end of expression (not an arrow).
The third one; fun foo/1 is also a fun reference (to a named function), and here it is indicated by the arity marker /1 that it is a reference.
So I suggested that the new fun reference fun foo(X) (anonymous reference to named function) might look much like the start of an anonymous fun definition fun Foo(X) -> body end.
|
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 |
|
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. |
Maria-12648430
left a comment
There was a problem hiding this comment.
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.
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.
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.
... which kind of drives home my point made re dropping or keeping the fun
There was a problem hiding this comment.
@Maria-12648430 it has been a long discussion, so I believe your comment means you prefer the fun prefix for clarity?
Co-authored-by: Maria Scott <67057258+Maria-12648430@users.noreply.github.com>
Clarify language around MFArgs and placeholders, and improve readability.
No description provided.