-
Notifications
You must be signed in to change notification settings - Fork 107
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
Isn't the bind operator similar? #2
Comments
It's similar, but my main problem with the bind operator is that it requires the use of the keyword |
The pipeline operator seems like it may be a lot more useful in a language with pervasive partial application (and libraries like Ramda). In that case it does have a distinct advantage. If a syntax for partial application is also added to JS, then I can see how this would work nicely. |
I'd have said "it allows us to use the |
@bergus I would argue JS has always intended to look like an object-oriented language, but I think I would be getting off topic at that point :) In any case, you make a good point. As you said, the current examples would not look much different with the bind operator (I do consider the implicit argument a con; I prefer to name my parameters). It may very well be that the bind operator is all we need. However, I would like to continue exploring this space to be sure. |
@spion For the record, partial application is super easy to add in JavaScript :) I do it all the time: Function.prototype.papp = function (...args) {
var fn = this;
return function () {
return fn.call(this, ...args, ...arguments);
}
};
var addFive = add.papp(5);
addFive(7) //=> 11
function add (x,y) { return x + y } This technique lines up quite nicely with the proposed pipeline op: var newScore = person.score
|> double
|> add.papp(7)
|> validateScore |
You're using |
@mindeavor I know, I just think that particular variant of partial application isn't always helpful because many JS libraries don't pay attention to argument order. Interestingly enough, the bind operator's main use case is in helping avoid attaching things on prototypes (which can cause all kinds of breakage when properties are enumerated or if there are name clashes) while still allowing for a readable chaining syntax import {papp, pipe} from 'fn-operators'
var newScore = person.score::
pipe(double)::
pipe(add::papp(7))::
pipe(validateScore)
// or
var newScore = person.score::
pipe(double)::
pipe(x => add(x, 7))::
pipe(validateScore) Uglier, yes - slightly. Does achieve the same thing. But we don't have to use pipe - what about compose, to create a function? import {papp, compose} from 'fn-ops'
let doubleAdd7Validate = double::compose(add::papp(7))::compose(validateScore)
doubleAdd7Validate(person.score) I suppose you could do the same with import {papp, compose} from 'fn-operators'
let doubleAdd7Validate = double |> compose(add |> papp(7)) |> compose(validateScore) But with that second function, papp's implementation always creates another extra function // pipe operator version
function papp(...args1) {
return function(f) {
return function(...args2)
return f.call(this, ...args1, ...args2)
}
}
} while the bind operator papp doesn't: // bind operator version
function papp(...args1) {
var f = this; // hidden argument to the rescue!
return function(...args2) {
return f.call(this, ...args1, ...args2)
}
} |
Your "pipe operator version" of Well the main point of the Object.defineProperty(Object.prototype, 'pipe', {
enumerable: false,
value: function (fn, ...args) {
return fn(this, ...args)
}
})
var result = "hello there"
.pipe( replace, ' ', '_' )
.pipe( x => x + "!" )
function replace (str, pattern, out) {
return str.replace(pattern, out)
} hmm, why are either of our proposals necessary again? |
As I said, the main point of Regarding the example, I don't think its a strawman, but its not really an argument for or against any of the two operators. What I'm trying to get at is that (baring some ugliness) the bind operator mostly subsumes the pipeline operator, at least in terms of capabilities, and if there isn't any support for partial application built into the language then the pipeline operator doesn't have a distinct advantage. (1) (1) hmm, on second thought, the lamda syntax is so short and clean that we don't gain much arr |> x => _.map(x, inc)
//vs
arr |> _.map(#, inc) I like both proposals (this one for how nicely it works with the fresh new FP libraries such as Ramda) , I just fear that if this one gets accepted that |
I agree with your (1); using the lambda syntax is just fine IMO. I know extending built-in prototypes is poor practice, but for the moment I'm re-evaluating whether we need new syntax at all for fluent chaining. For example, wouldn't this accomplish the same as the bind operator's chaining behavior? Object.defineProperty(Object.prototype, 'bindPipe', {
enumerable: false,
value: function (fn, ...args) {
return fn.apply(this, args)
}
})
var result = "hello there".bindPipe( replaceSpaces, '_' )
function replaceSpaces (out) {
return this.replace(' ', out)
} Sure we're extending the prototype here, but if |
@mindeavor Extending native prototypes is just bad practice in all cases where you're trying to define your own chainable syntax. With the bind syntax, or even with the pipeline operator, one could import libraries that support nice chaining APIs without having any opportunity for conflicts. The argument that |
That's a little black and white, isn't it? Perhaps I'm being unclear; by standardized, I mean accepted into the ES.next spec. I extend the prototype to emulate such a proposed scenario. In other words, for argument's sake, I propose |
@mindeavor Probably not for that case, but the two added syntaxes would allow for custom chainable functions which potentially wouldn't be standarized either ever or any time soon. Personally, I prefer just using a higher-order |
Isn't the most obvious benefit of the bind operator that defining new prototypes becomes redundant? |
Are you referring to custom functions that wouldn't be possible to use with @barneycarroll Actually a string literal was the first example I tried it with. It seems to work on my end.. |
@mindeavor I guess I can concede that it'd be possible with such method defined on all objects, but it's more verbose and unlikely to become a standard since AFAIK defining a new method on |
The biggest problem with defining anything new on Object.prototype is the use of objects as dictionaries. The last addition ( |
@spion FWIW Javascript has given us a multitude of ways of avoiding this problem space in recent years:
|
@barneycarroll then are there any other reasons why |
AFAIK |
"ugly kludge" doesn't explain the reason, but I looked it up and it seems like one of the reasons is indeed breaking existing dictionaries (tables): https://esdiscuss.org/topic/standardizing-proto (last message) |
I raise the issue because you're clearly informed and rallying functional style in JS. I'd just love to see that energy as a community directed toward root issues rather than ancillary concerns. |
Demo @ https://jsbin.com/kujeha/4/edit?html,js,console // -------------------------------------------------
// Tests
// -------------------------------------------------
@util class Calculations
{
sum (x, y)
{
return x + y
}
multiply (x, y)
{
return x * y
}
}
console.log
(19
::Calculations.sum (1)
::Calculations.multiply (5)) // 100
console.log
(Calculations.sum (19) (1)) // 20
let calcStuff = compose
(Calculations.sum (2), Calculations.multiply (20))
console.log
(8::calcStuff()) // 200
console.log
(calcStuff (3)) // 100
// --------------------------------------------------------
// @util
// class decorator that statically defines,
// auto-curries and `::` enables its functions
// --------------------------------------------------------
// enables a function to be used both with
// :: and normally (auto curried when normal)
// eg: sum (x, y) => x + y
// sum (1) (2) == 3
// (3)::sum (1) == 4
function multiFunction (_function)
{
// wraps the function and detects
// if :: is being used. Does this by checking
// the type of this.constructor, if it's the same
// as the wrapped function that means :: wasn't
// used so it's treaded as a normal function call
function wrapped (...params)
{
if (this.constructor.name == "wrapped")
return curried.apply (this, params)
else
return _function.apply (null, params.concat (this))
}
// if you're not using :: then the
// function will be auto curried
function curried (...args)
{
if (args.length == _function.length)
return _function.apply (this, args)
else
return (...params) =>
curried.apply (this, [].concat (args, params))
}
// this will be the function
// defined in the decorated class
return wrapped
}
// decorator that wraps class functions
// and turns them static so they can be
// used without instantiating the class
function util (Class)
{
Object
.getOwnPropertyNames (Class.prototype)
.forEach (property =>
Class[ property ] =
multiFunction (Class.prototype[ property ]))
return Class
}
// simple compose function
function compose (...functions)
{
return multiFunction (function (value)
{
return functions.reduce ((t, fn) => fn (t), value)
})
} Pipeline operator is obviously better, none of the above Demo @ http://bit.ly/1QVIrbx // -------------------------------------------------
// Tests
// -------------------------------------------------
let Calculations = {
sum : (x, y) => x + y,
multiply : (x, y) => x * y
} |> curry;
2
|> Calculations.sum (3)
|> Calculations.multiply (5)
|> console.log // 25
// -------------------------------------------------
// Curry
// -------------------------------------------------
function curry (obj)
{
Object.keys (obj).forEach (property =>
{
let original = obj[ property ]
obj[ property ] = function curried (...args)
{
if (args.length == original.length)
return original.apply (this, args)
else
return (...params) =>
curried.apply (this, [].concat (params, args))
}
})
return obj
} |
Depends on what your goals are. JS doesn't support currying out of the box, so if you take that out of the equation: https://jsbin.com/xaliyiyaho/1/edit?html,js,console One unfortunate thing is that we don't have thin arrow lambdas, which would reduce the verbosity of composition here. On the other hand, this is nice but still prohibitively costly (have you tried comparing performance? :) |
I'm a fan of both proposals, but I think they're orthogonal. The pipe operator is more about operating with certain types of functions, The function bind operator is more about extending existing objects and types. In fact, I think the biggest hang up with this one is the desire to have something more like C# extension methods without adding syntax that's too crazy. The pipe operator is much, much simpler in concept than the function bind operator. Again, I like both of them though, and I'd love to have both. |
@spion True, thin arrow lambdas would be very nice 👍 @Blesh Yeh, pipeline is much simpler. The reason i found out about the bind operator was searching for something similar to C# extension methods, also in Kotlin there are Extension Functions If we had both i would always use pipeline, as it would work very nicely with lodash and lodashFP. I think i would never use bind operator. Bind operator is very good for OOP, Pipeline for Functional... On the bind operator proposal i see:
So lots of OOP related tasks. Yeh, bind operator for JavaScript, and for the current generation which is of majority OOP programmers, seems to be more important. I would be happy with any being implemented, but prefer pipeline for its simplicity. |
You'd be surprised. If you want to add a method to a third party class, extension methods are king. The tricky business the TC39 needs to figure out around that is how it will integrate with prototypal inheritance, etc. I think they're looking at changing it quite a bit, but I could be mistaken. Either way, I LOVE both operators, and I wish I could get them both. I suspect there will be a lot of resistance to either one though. Because they're functional additions to a rather imperative language, and they're not very "C-like" or whatever. |
What if everyone focused on implementing Macros instead of all the operators? (which is only planned for ES2017) Then we wouldn't need to bloat the core and let teams/projects decide on their own operators 😄 |
@thelambdaparty I'm for that too. |
I loathe prototypes, class, this, bind (and yes, I now precisely how they work), so to me, |
The intention of this proposal is to largely subsume the bind operator. |
Page: https://github.com/zenparsing/es-function-bind
Works with the
this
argument instead of the first argumentAn extra nice property is that it allows creating bound functions
The text was updated successfully, but these errors were encountered: