Open ysitbon opened 6 years ago
Maybe this silly example can help
// partial + ternary operator + optional chaining + nullish coalescing in the same call.
const doStuff = (x, y, z, w) => {
// do something with x, y, z and w
}
const curriedDoStuff = doStuff(?, v.?a ?? 0, v.b > 10 ? 10 : 0, ?);
And I think we can easily be more cryptic.
Just to name an alternative: how about the dot, like in R?
const curriedDoStuff = doStuff(., v.?a ?? 0, v.b > 10 ? 10 : 0, .)
const curriedDoStuff = doStuff(*, v.?a ?? 0, v > 10*b ? 10 : 0, *)
but the *
is nice, and both are better than the ?
How about something like a string format? It would allow you to specify which argument you want:
const curriedDoStuff = doStuff(%, v.?a ?? 0, v > 10*b ? 10 : 0, %...)
const curriedDoStuff = doStuff(%0, v.?a ?? 0, v > 10*b ? 10 : 0, %1)
const curriedDoStuff = doStuff({0}, v.?a ?? 0, v > 10*b ? 10 : 0, {...})
const curriedDoStuff = doStuff({0}, v.?a ?? 0, v > 10*b ? 10 : 0, {1})
I really, really wish that the underscore character was available!
Since it cannot be used as a variable character, I propose using the arobase character @ for place-holder and @@ for rest placeholder. We then even use numbered place holder (@1, @2, etc.) to change the order of expected arguments in the resulting function like:
const f = (x,y,z,w) => ( (x * y) - ( z *w) )
// "ordered placeholders"
const g = f(@, 12, @, 14)
// then g = ( a1, a2 ) => f( a1, 12, a2, 14)
g(11, 13) === f(11, 12, 13, 14)
// "rest placeholder"
const h = f( @@, 14)
h(11, 12, 13) === f(11, 12, 13, 14)
// "numbered placeholders"
const k = f( @3, @1, 13, @2 )
// equivalent to
const k = ( a1 , a2, a3) => f( a3, a1, 13, a2)
k(21, 22, 23) === f( 23, 21, 13, 22 )
// even
lodash_filter( @2, @1) == ramda_filter
lodash_filter == ramda_filter(@2,@1)
Moreover, @ naturally sounds like "arg" (ok pittyful argument :P)
Moreover, @0
could represent the complete arguments
passed to the partial function.
Cannot think of a use case but could be possible
Since it cannot be used as a variable character, I propose using the arobase character @ for place-holder
This would be ambiguous with future support for decorators on arrow functions and function expressions.
Well, the question mark is already in use and for 90% of time it's used to deal with null values ( x==null ? 0 : x
) or optional properties on objects, so it makes complete sense to use it on both proposals with ??
and object.prop?.value
......
However it makes no sense to use it as new partial operator which is totally different thing: it is actually more a decorator thing than an operator thing!
Since decorators are meant to modify the surrounding code, I found it leggit to use a decorator-like synthax for partial application synthax.
So I propose finally:
@_
for ordered placeholders@1, @2, @3,...
for numbered placeholders, which define order of arguments on the created function.@...
for the "rest" placeholderFinally, all of this could be short synthax for decorators, for example: @partial(), @partial(1), @partial(...)
Interesting single-character tokens on a standard keyboard already have a use in JavaScript, unfortunately:
~
- ones' complement (prefix)!
- boolean negation (prefix)@
- decorators (prefix)#
- private name (prefix)\
- escape (prefix)%
- modulo (infix)^
- bitwise XOR (infix)&
- bitwise AND (infix)*
- multiplication (infix), yield (prefix/infix), generator methods (prefix)-
- numeric negation (prefix), subtraction (infix)+
- numeric plus (prefix), addition (infix)|
- bitwise OR (infix):
- object assignment, label (prefix), conditional (ternary);
- statement separator (postfix)<
- less than (infix)>
- greater than (infix)/
- division (infix), regexp (prefix+postfix).
- property access (infix), decimal number (prefix)`
- template literal (prefix+postfix)'
- single quote (prefix+postfix)"
- double quote (prefix+postfix)( )
- parens (prefix+postfix){ }
- block/object literal (prefix+postfix)[ ]
- array (prefix+postfix), element access (postfix)$
- identifier_
- identifier?
- conditional (ternary)Anything that is a prefix token is out due to forwards incompatibility with possible future features such as positional placeholders. Balanced tokens (such as `
, '
, "
, ( )
, { }
, and [ ]
) are out due to parsing issues with lookahead. Identifiers (like $
and _
) are out as they are valid existing syntax. That leaves us with only infix and ternary tokens: %
, ^
, &
, |
, <
, >
, and ?
. I ruled out &
as I have a strawman for a "pass-by-reference" proposal that would leverage &
for consistency with other languages. I ruled out the |
, <
, and >
characters as they could be confusing when paired with pipeline (and any possible future backpipe <|
operator). That leaves us with ?
, %
and ^
.
Of the remaining three characters, ?
seems the most like a "what goes here?" token and is the main reason I chose it for the proposal.
As to ambiguity with other proposals, the ?
character is currently used in two proposals:
?.
)??
)The optional chaining proposal is one reason why I did not allow ?.x()
as a means of marking the this
receiver for a placeholder. However, it looks like optional chaining may be changing their tokens for consistency either to ??.
or ?..
. If that is the case, the parsing ambiguity goes away and is instead limited only to possible user confusion (in which case the ??.
token for optional chaining would be preferred), meaning that using ?
as the receiver becomes a possibility.
As with the optional chaining proposal, the coalesce proposal is considering changing their token to ??:
which removes even more ambiguity.
As far as "too many question marks" that all depends on coding style. You can already have perfectly legal javascript with +a + b++ + ++ c
or [+[]][++[]] - +[] + []++
(though I obviously wouldn't recommend such practice). I imagine most uses of this sigil when combined with other uses of ?
will not be no more complex or hard to read than any other token we would choose:
ar |> map(?, x => x > 0 ? true : false);
ar |> map(?, x => x ?? "missing");
As such, I believe that ?
is still the best token for this proposal.
Also, regarding using *
or other infix operators:
I have considered eventually proposing an "operator function" shorthand syntax to pair with pipeline and partial application as something like ar |> map(?, {*} 2)
as a shorthand for ar |> map(?, (x) => x * 2)
and wanted to avoid confusion with such operations.
I haven't put this proposal before the committee yet as that would be far too much syntax to propose all at once.
Ok..... as I understand it, the @ is for decorators, but a decorator has the form @valid_function_identifier
; since a single number is not a valid identifier, nor is the @ character or the three-dots, we can use
the following:
,
or a )
because we are in a function call expression, so not a decorator)I admit I didn't understand this sentence
Anything that is a prefix token is out due to forwards incompatibility with possible future features such as positional placeholders
and I agree that the question mark is the best token if we cannot do anything against it.
I'm worried that using @
would be ambiguous with other proposals in progress (such as value types and suffix notation) when combined with decorators and would also limit us with future directions for this feature.
@rbuckton I appreciate this thorough analysis. Have you considered using multiple character tokens?
or even <?>
@littledan Other than for ...
, no. I didn't feel it was necessary.
@ljharb I would be concerned that this would be confusing if I ever attempted to introduce shorthand operator functions.
I considered (?)
as a syntax to attempt to bind the this
receiver but it feels too fragile:
// ok
(?).x(); // a => a.x()
(?).x(?); // (a, b) => a.x(b)
// not ok
(?).x.y();
(?)();
x((?));
or even <?>
@ljharb I gotta say it would improve the readability a bunch but it seems over the top to me, it should be simpler.
const addOne = add(1, <?>);
addOne(2); // 3
const addTen = add(<?>, 10);
addTen(2); // 12
let newScore = player.score
|> add(7, <?>)
|> clamp(0, 100, <?>);
I think the contrived example in the OP would look just as confusing with any other syntax.
IMO ?
is perfect, but *
is the best of all the alternatives proposed. Both *
and ?
are universal wildcard symbols.
Unfortunately *
has too overloaded of a meaning to be used, and if we ever considered allowing ?()
or ?[x]()
(per https://github.com/tc39/proposal-partial-application/issues/23#issuecomment-364716419), then it would conflict with yield
/yield *
. If I had to choose another token with a placeholder-like semantic meaning I would probably choose %
given its use in string expansion (a la printf
and console.log
) and environment variable expansion on some platforms.
@rbuckton I like %
; would %this.map(x => x + 1)
work as well (related to #23).
%
is a great idea for a placeholder. Clojure also uses %
for its bare anonymous-function form’s placeholders. For instance, Clojure #(+ % 5)
is equivalent to Clojure (fn [x] (+ x 5))
.
(As an aside, %
also may be a good choice for the placeholder of the alternative smart-pipelines proposal, too—perhaps one more palatable than its current placeholder #
. I’ll add it to the table in tc39/proposal-pipeline-operator#91. With the smart pipelines proposal, +> f(%, 5)
would be equivalent to x => f(x, 5)
(or this partial-application proposal’s f(?, 5)
). Likewise, +> %.map(f)
would be equivalent to x => x.map(f)
(or this proposal’s %this.map(f)
).)
Semantically, ?
just feels right -- better than %
or *
or something else.
@mlanza Semantically? Please, do not mix-up with personal preferences. %
and *
they also have a long history as placeholder symbols https://en.wikipedia.org/wiki/Wildcard_character
@ysaskia - I know that %
and *
have been used as wildcards in like
clauses to represent the unknown portion of a string pattern.
But ?
feels to my mind more like a singular slot into which a value can be plugged as with Mad Libs. So when I say "semantically," no, it's not to a universal standard of meaning, but it's based on my experience with the world. It may not be the correct and only view, certainly, but that's why we discuss. The hope is to arrive at a shared consensus surrounding a semantic that makes sense to most. If more people feel otherwise, I'm okay with that.
Even #
which I've also seen recommended feels better. My mind has too strongly associated %
and *
with the notion of partial matches not slots. It doesn't make my case, but even Datomic uses ?
.
To be honest, I kind of like the original proposal to use ...
. I personally find the question mark sigil to be visually "noisy" and hard to distinguish from an identifier at a glance, but that's just me.
...
definitely implies that it captures or is replaced by multiple things. It’s even already used in JS for exactly that.
?
and *
only slightly imply 0/1 or “any amount”, respectively (since they’re used in regexes like this, and *
is used in python like ...
in JS)
Of course, and I understand that. I only meant to point out that, even skimming the example snippets here, I often have to pause and look a bit closer to distinguish the ?
from a legal identifier and therefore it’s not immediately obvious “will this function be called or not?” I don’t know how much of a problem that will be in practice, but it definitely concerns me.
From this perspective, I find *
to be preferable to ?
, for what it’s worth.
How important is it to not use valid existing syntax? Technically adding let
and const
were breaking changes... var let = 2
even still works.
Most ESNext users likely don't import _
directly, and there could be simple codemods to help people transition.
@rajington they weren't breaking changes because it still works.
Couldn't _
(or $
) still work then? Could lodash/underscore/etc. var _ = foo
still set the variable?
They could set the variable. Using the variable is the problem. Let's say we have
function getLodashVersion(lodash) {
return lodash.VERSION;
}
console.log(getLodashVersion(_))
where _
points to a global lodash instance. Right now, this prints the lodash version string, like "4.17.11"
. If this proposal were to pass with _
as the token, it would instead mean (roughly, omitting some details for brevity)
console.log((_placeholder) => getLodashVersion(_placeholder))
i.e. it prints a new function instead of the result of calling the original function.
?
isn't a variable, it is a marker for a missing parameter. I wouldn't recommend using any valid ECMAScript identifier in its place as it could be ambiguous.
Ignoring the other proposals, what would be the expected behaviour if I used the ternary to conditionally apply the argument placeholder? i.e.
const addOne = add(1, foo ? bar : ? );
I mean, it'd have to work right? I can't think of other examples where using the ternary operator to conditionally pass an argument would be disallowed. It's really ugly though if ? is the truthy case.
const addOne = add(1, foo ? ? : bar );
I like the question mark above all else if *
isn't viable, but this stands out as my main concern.
It wouldn't work: ?
can only used to replace a parameter, not an expression inside another expression which is passed as a parameter. Only add(1, ?)
is valid.
Here's a similar example with ES6 syntax: fn(foo ? a : ...b)
. This is invalid syntax because ...
is only legal in an argument list, not an expression position. If you wanted to make it conditional, you would have to make the entire call expression one of the branches:
const result = foo ? add(1, bar) : add(1, ?);
I am biased towards using &ARGUMENT_NUMBER
like Elixir does but I am not sure what will be the issue in the parser and &
bitwise operator.
let newScore =
player.score
|> add(7, &1)
|> clamp(0, 100, &1);
What about ::
? It looks like a square, which is common for "something can be written inside". Unlike most repeated symbols, ::
is parsed by the brain not like "two colons", but like "four dots" - so it's more visually distinctive than any other character.
10
|> add(1, ::)
|> mul(::, 2)
The binding proposal that was gonna use it doesn't seem to go anywhere.
::
conflicts with the bind operator proposal, which I'm hoping still happens someday
10::add(1)::mul(2)
There is another option to use \0
, \1
like regex.
10 |> add(1, \0) |> mul(\0, 2)
var work= (key, value)=>{...}
[...].map(work(\1, \0));
not as good as @
, but better then ?
The best argument against ?
is that its meaning is already overloaded to a strong degree. Ternary, nullish, optional chaining. Adding one more meaning will absolutely start to drain developers of their at-a-glance comprehension.
I really don't see any reason the parser would have a problem using a special character with a prefix usage. So long as #
or @
isn't followed by identifier characters, they are obviously not private values or decorators.
%
is a perfectly reasonable character, and rarely used as the modulo operator, so it makes a good choice, though after having used the smart pipeline operator for the last six months, I have to say, the brickish #
is incredibly easy to parse, and additionally, its functional use here is unlikely to cross paths with OOP private name usage elsewhere.
@rbuckton since #37 was redirected here, I wanted to ask about the last comment you made there re @
I'm concerned about using @ as it steps on the syntactic space for decorators. While its true that @ on its own and @ DecimalDigit don't conflict currently, if we needed any other special forms for placeholders that included keywords (such as the ?this proposal in #23) we could run into the design space for the syntactic decorators proposal.
If we used @
, did you have any special forms in mind requiring keywords/identifiers? It's a little hard to follow the threads re @
suggestions but it seems to me @
on its own could replace ?this
, and @0
, @1
etc. could replace ?
as argument placeholders.
e.g.
foo
|> add(@0, 2)
|> fetchData(@0)
|> await
|> @.json()
|> await
As mentioned, there's no conflict with decorators there and it's pretty intuitive.
Are there other use-cases that would be needed in future where @
would run in to trouble? Even if so, it'd be interesting to see if they could be solved with something like @:this
or @:someIdentifier
.
Sorry if this has been covered in the other issues, but I couldn't find anything specific about what the problem would be with @
.
There is another option to use \0, \1 like regex.
It also looks like lambda, and used for this purpose in Haskell for example.
What concerns me even more than abusing the poor ?
is the overloading of ()
without any hint of partial application. It's very easy having piece of code
array.map(clamp(-50, 100, ?));
to run into confusion just by wrapping it into another function:
array.map(Math.abs(clamp(-50, 100, ?)));
I really love this proposal and I want it to be implemented but... I believe that the question mark is not a good choice. We already have the existing ternary operator and probably later we'll have the implementation of the 2 proposals optional chaining and nullish coalescing. Obviously, the futur js code will looks like very ugly hieroglyphs.
Why not an asterisk or another special character ?
It's just my personal opinion but maybe it's just me. Too much question marks...