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

Use a wrapper to indicate something follow :: is a expression but not an identifier #55

Open
Fenzland opened this issue Nov 27, 2019 · 13 comments

Comments

@Fenzland
Copy link

problem

When we read some code like this:

document.querySelectorAll("div.myClass")::find("p")::html("hahaha");

We will think the find and html is a identifier like something follow . .
But it's actually an expression. So it must be some using case like this:

document.querySelectorAll("div.myClass")
  ::libA.find("p")  // means `::(libA).find("p")` or `::libA).find("p")` or `::(libA.find)("p")` or `::(libA.find("p"))`?
  ::(condition?libB.html:libB.text)("hahaha"); // it's seems good, but looks like a function call.

That becomes confusing.

solution

So I think it's necessary to wrap it with something,

  • not [] they are used to wrap identifier expression;
  • not (), they are used to wrap a normal expression and looks like a function call;
  • {} and <> are okay, I prefer to use {}.

The code becomes:

document.querySelectorAll("div.myClass")::{find}("p")::{html}("hahaha");

document.querySelectorAll("div.myClass")
  ::{libA.find}("p")
  ::{condition?libB.html:libB.text}("hahaha");

With a little price of two characters, it becomes more readable and extendable.

a step more

Once we avoid to use a identifier syntax as an expression. We can let it do the right thing, to act as a identifier, get method from prototype and bind to the object. Example:

const foo= document.getElementById( 'foo' );

// with identifier
const setAttributeToFoo= foo::setAttribute;

// with identifier expression
const removeAttributeFromFoo= foo::['removeAttribute'];

// with function expression
function addClass( className ){ this.classList.add( className ); }
const addClassToFoo= foo::{addClass};

There are a new problem with the left hand of assign expression. A TypeError will solve the problem.

foo::bar= 1; // Just throw a TypeError
@hax
Copy link
Member

hax commented Nov 29, 2019

If I understand correctly, you are proposing:

  • Replace current ::foo.method with foo::method
  • Replace current ::foo[bar] with foo::[bar]
  • Replace current foo::bar with foo::{bar}
  • Replace current foo::bar(...args) with foo::{bar}(...args)

@Fenzland
Copy link
Author

Fenzland commented Dec 1, 2019

If I understand correctly, you are proposing:

  • Replace current ::foo.method with foo::method
  • Replace current ::foo[bar] with foo::[bar]
  • Replace current foo::bar with foo::{bar}
  • Replace current foo::bar(...args) with foo::{bar}(...args)

Leading :: is not a good idea because:

  • :: and . are not two separeted operator but the the same operator like ? and :. To design a dyadic operator with a syntax like a ternary, oh no;
  • what ::foo.bar.baz means? (::foo.bar).baz or ::(foo.bar).baz;
  • leading operator is unfriendly to the people who omit the semicolons, and that's not a unary operator, leading is not necessary.

@hax
Copy link
Member

hax commented Dec 1, 2019

@Fenzland I agree with you that leading :: has some issues. I'd rather drop it or split it to a separate proposal.

This proposal actually have three parts:

  • Method extraction (::foo.bar / ::foo[bar])
  • Syntax sugar of foo.bind(bar) (bar::foo)
  • Virtual method (foo::bar(...args))

After past 4 years discussion, I really feel these three things have very different use cases and only connected weakly, we'd better split them and should not overload :: too much. Our question is how?

For example, in your suggestion, the third use case (virtual method) now become foo::{bar}(...args) which need many brackets. But in all previous discussion in this repo, most believe virtual method is the most important use case.

On the other hand, method extraction have a very limited usage, the only pattern is something like addEventListener(type, obj::method) (use the syntax you suggest), any complex combination like obj::method.prop is meaningless. It's not very economical to waste the best syntax option on it!

Actually, if you consider virtual method, we could easily create a util method allow us write obj::methodOf(methodName) for method extraction, or even obj::getterOf(propName) which current proposal can't do. So personally I feel we don't need special syntax/operator for method extraction.

@ljharb
Copy link
Member

ljharb commented Dec 1, 2019

(Method abstraction is the one use case that pipeline doesn’t address)

@Fenzland
Copy link
Author

Fenzland commented Dec 2, 2019

@hax Thank you for your patience. I've not got knowledge about the discussion history indeed. But only talk about the result syntax.

  • we already put identifiers after ., now to put expressions after :: is confusing
  • bar.bind(foo)(...args) to foo::bar(...args) but bar.bind(foo) to bar::foo it's really unconsidered
  • leading ::
  • :: - . or :: - [] ternary operator style

@Fenzland
Copy link
Author

Fenzland commented Dec 2, 2019

On the other hand, I don't think the virtual method is so important use case, it can be totally substituted by pipeline operator.

function doubleEach()
{
    return this.map( x=> x*2, );
}

[ 0, 1, 2, ]::dobleEach(); // [ 0, 2, 4, ]

function timesEach( n, )
{
    reutrn this.map( x=> x*n );
}

[ 0, 1, 2, ]::dobleEach( 3, ); // [ 0, 3, 6, ]

can replace with:

const doubleEach= array=> array.map( x=> x*2, );

[ 0, 1, 2, ] |> dobleEach; // [ 0, 2, 4, ]

const timesEach= n=>
    array=> array.map( x=> x*n, )
;

[ 0, 1, 2 ] |> timesEach( n, );

And I believe that library authors will like pure functions more then virtual methods, especially in the future ages.

I agree with that the usage of method extraction is limited too. But limited not means useless. Only addEventListener(type, foo.bar.obj::method) is so useful, it will fill the gap between OOP and FP (I believe that FP will be more and more important in the world of JavaScript). And that's more friendly to the newbies then addEventListener(type, ::(foo.bar.obj).method) or addEventListener(type, foo.bar.obj.method.bind(foo.bar.obj)).

@hax
Copy link
Member

hax commented Dec 2, 2019

we already put identifiers after ., now to put expressions after :: is confusing

@Fenzland Yeah, I really agree with you in this point! This is also the key change I want to make in future proposal. There are some issues on foo::very.complex.expression(), for example, what's the precedence of :: and .. You already mention it in a very good example: ::libA.find("p") // means `::(libA).find("p")` or `::libA).find("p")` or `::(libA.find)("p")` or `::(libA.find("p"))`?

So I actually agree we should limit foo::bar only apply to identifier, so foo::a.b simply mean (foo:a).b. The only disagreement is which feature use this best syntax.

@hax
Copy link
Member

hax commented Dec 2, 2019

  • but bar.bind(foo) to bar::foo it's really unconsidered

I also agree with you in this point!

I agree with that the usage of method extraction is limited too. But limited not means useless. Only addEventListener(type, foo.bar.obj::method) is so useful, it will fill the gap between OOP and FP (I believe that FP will be more and more important in the world of JavaScript). And that's more friendly to the newbies then addEventListener(type, ::(foo.bar.obj).method) or addEventListener(type, foo.bar.obj.method.bind(foo.bar.obj)).

Yes, I agree all of that, actually I already mentioned in last comment that method extraction could be expressed like addEventListener(type, foo.bar.obj::methodOf('method')). Though it seems not concise as foo.bar.obj::method, I believe it's still clear enough, and have the bonus of supporting obj::getterOf(prop). Note I'm not against special syntax for method extraction, it still possible in the future (for example, groovy-like syntax foo.&bar), but we'd better be cautious about any new syntax, because there no much good operators available in JS.

@hax
Copy link
Member

hax commented Dec 2, 2019

On the other hand, I don't think the virtual method is so important use case, it can be totally substituted by pipeline operator.

Yes pipeline op seems very near to virtual methods , but I really feel this is actually a misunderstanding in past discussion and also one of the reason why this proposal is staled. We are too eager to use the same syntax to solve the needs of both OO and FP world.

Actually they are very different, for example, we already discussed the issue of high precedence (OO) VS. low precedence (FP), identifier (OO) VS. expression (FP) in previous comments. You can find more differences if you look deep, for example the most important one: this parameter (OO) VS. curried last parameter (mainstream FP pipeline) VS. first parameter (elixir-style pipeline).

And I believe that library authors will like pure functions more then virtual methods, especially in the future ages.

This is very suspicious. I really love FP, but we should notice that JS have many limitation on adapting FP style, for example, still miss currying, tail calls in the core language, which may affect the design of other fp features (like pipeline op, partial applications) in various aspects. And we should recognize that all JS built-in libraries and web platform APIs are OO-style. So we should not advocate FP features by disregarding requirements and use cases of OO-style programming.

We should also realize in many times programmers are forced to use "FP-like" style just because we do not provide virtual method. For example, underscore/lodash started from jQuery-style method chaining. But unfortunately we do not have virtual methods so libraries can only use clumsy wrapper, which only fit in jQuery (normally produce effects not return values, and not very performance-sensitive) but not general util libs.

So IMO many libs are FP-like style not because they love FP but just because FP-like style is the only reasonable way. It's a typical "Chicken or the egg" problem. If you check other languages you will find the other solutions, for example, "extensions" (which is just a set of virtual methods) in C#, Scala, Kotlin, Swift. Actually extension is a very wanted feature (from 2014) and have a long discussion in TypeScript community (issue no is 9!), but there is no viable way except "bind operator", and we already see that this proposal is stalled due to the issues we found and IMO mix the requirements of OO/FP is one of the root cause.

@Fenzland
Copy link
Author

Fenzland commented Dec 3, 2019

@hax Thank you for considering my suggestions. I'm happy to make helps.


So I actually agree we should limit foo::bar only apply to identifier, so foo::a.b simply mean (foo:a).b. The only disagreement is which feature use this best syntax.

That will be limited to use, how about use a wrapper to apply a expression? Like parameter of arrow functions, no wrapper for identifiers, with wrapper for complex structures.


We should also realize in many times programmers are forced to use "FP-like" style just because we do not provide virtual method.

That persuade me.

it still possible in the future (for example, groovy-like syntax foo.&bar)

I agree with this. Something like leading :: will hinder this proposal to go. Maybe we should just focus on virtual methods on this proposal and leave other features like method extraction to future proposals.

@Fenzland
Copy link
Author

Fenzland commented Dec 3, 2019

for example the most important one: this parameter (OO) VS. curried last parameter (mainstream FP pipeline) VS. first parameter (elixir-style pipeline).

Not only JavaScript can become more functional, FP need become more inclusive. Why not try to make a future that methods with this, last parameter functions and first parameter functions can play together.

I'm doing some attempts for this: https://github.com/Fenzland/BetterJS#functional-programming

@hax
Copy link
Member

hax commented Dec 3, 2019

That will be limited to use, how about use a wrapper to apply a expression? Like parameter of arrow functions, no wrapper for identifiers, with wrapper for complex structures.

@Fenzland If I understand correctly, u mean we could support foo::bar for one feature and foo::(bar) or foo::{bar} or foo::[bar] for other features. I believe it's possible, but as we see foo::bar() as virtual method is already very powerful --- in many cases , you could use foo::myfunc(bar) instead of foo::(bar).

So there are two questions:

  1. If we already could use foo::myfunc(bar), why we need a special syntax foo::(bar)?
  2. Assume there are several useful features: foo:feature1(bar) foo:feature2(bar) foo:feature3(bar)... Which one is most useful and deserve special syntax foo::(bar)?

Personally, I feel we should only introduce a new syntax for very useful features and/or normal virtual methods can't do. And how we know that? We need to first allow developers try the ideas in the wild.

This is why I think we'd better only focus on foo:bar for virtual methods in the first, and consider other forms later.

@hax
Copy link
Member

hax commented Dec 3, 2019

Why not try to make a future that methods with this, last parameter functions and first parameter functions can play together.

Yeah, I think it should be one of the goal.

I'm doing some attempts for this: https://github.com/Fenzland/BetterJS#functional-programming

And I believe virtual methods could actually help you ! For example,

instead of

[ 0, 1, 2, ]['|>'](
	map( x=> String (x*2), ),
	join( '~', ),
	slice( 1, ),
);

We could just use

[ 0, 1, 2, ]::pipe(
	map( x=> String (x*2), ),
	join( '~', ),
	slice( 1, ),
);

Another example:

const $ = Symbol('placeholder')
const partial = function (...args) {
	return x => this(...args.map(arg => arg === $ ? x : arg))
}

function div(a, b) { return a / b }

const reciprocal = div::partial(1, $)  // very same as `div(1, ?)` (partial application proposal)

reciprocal(2) // 0.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants