tc39 / proposal-partial-application

Proposal to add partial application to ECMAScript
https://tc39.es/proposal-partial-application/
BSD 3-Clause "New" or "Revised" License
1.02k stars 25 forks source link

Too much question mark #21

Open ysitbon opened 6 years ago

ysitbon commented 6 years ago

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 ?

const add = (x, y) => x + y;
const mul = (x, y) => x * y;

10
  |> add(1, *)
  |> mul(*, 2)

It's just my personal opinion but maybe it's just me. Too much question marks...

ysitbon commented 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.

flying-sheep commented 6 years ago

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 ?

borela commented 6 years ago

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})
CrossEye commented 6 years ago

I really, really wish that the underscore character was available!

mathias-a-la-basse commented 6 years ago

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)

mathias-a-la-basse commented 6 years ago

Moreover, @0 could represent the complete arguments passed to the partial function. Cannot think of a use case but could be possible

rbuckton commented 6 years ago

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.

mathias-a-la-basse commented 6 years ago

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:

Finally, all of this could be short synthax for decorators, for example: @partial(), @partial(1), @partial(...)

rbuckton commented 6 years ago

Interesting single-character tokens on a standard keyboard already have a use in JavaScript, unfortunately:

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.

rbuckton commented 6 years ago

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.

mathias-a-la-basse commented 6 years ago

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:

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.

rbuckton commented 6 years ago

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.

littledan commented 6 years ago

@rbuckton I appreciate this thorough analysis. Have you considered using multiple character tokens?

ljharb commented 6 years ago

or even <?>

rbuckton commented 6 years ago

@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((?));
Mouvedia commented 6 years ago

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, <?>);
scf4 commented 6 years ago

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.

rbuckton commented 6 years ago

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.

mAAdhaTTah commented 6 years ago

@rbuckton I like %; would %this.map(x => x + 1) work as well (related to #23).

js-choi commented 6 years ago

% 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)).)

mlanza commented 6 years ago

Semantically, ? just feels right -- better than % or * or something else.

ysitbon commented 5 years ago

@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

mlanza commented 5 years ago

@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 ?.

fatcerberus commented 5 years ago

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.

flying-sheep commented 5 years ago

... 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)

fatcerberus commented 5 years ago

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.

rajington commented 5 years ago

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.

ljharb commented 5 years ago

@rajington they weren't breaking changes because it still works.

rajington commented 5 years ago

Couldn't _ (or $) still work then? Could lodash/underscore/etc. var _ = foo still set the variable?

noppa commented 5 years ago

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.

rbuckton commented 5 years ago

? 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.

dev6james commented 5 years ago

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.

nicolo-ribaudo commented 5 years ago

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.

rbuckton commented 5 years ago

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, ?);
yordis commented 5 years ago

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);
lazarljubenovic commented 4 years ago

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.

obedm503 commented 4 years ago

:: conflicts with the bind operator proposal, which I'm hoping still happens someday

10::add(1)::mul(2)
Fenzland commented 4 years ago

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 ?

mikestopcontinues commented 4 years ago

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.

mmkal commented 3 years ago

@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 @.

stiff commented 3 years ago

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, ?)));