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

Isn't the bind operator similar? #2

Closed
spion opened this issue Dec 6, 2015 · 31 comments
Closed

Isn't the bind operator similar? #2

spion opened this issue Dec 6, 2015 · 31 comments

Comments

@spion
Copy link

spion commented Dec 6, 2015

Page: https://github.com/zenparsing/es-function-bind

Works with the this argument instead of the first argument

An extra nice property is that it allows creating bound functions

@gilbert
Copy link
Collaborator

gilbert commented Dec 6, 2015

It's similar, but my main problem with the bind operator is that it requires the use of the keyword this to do function chaining. Previous conversations here: https://esdiscuss.org/topic/the-pipeline-operator-making-multiple-function-calls-look-great#content-3

@spion
Copy link
Author

spion commented Dec 6, 2015

this isn't really a keyword, it is the natural "main" function argument in JavaScript. The reason why it hasn't been used in that way is the lack of syntax to do it, which is precisely what :: would solve.

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.

@bergus
Copy link

bergus commented Dec 7, 2015

main problem with the bind operator is that it requires the use of the keyword this to do function chaining

I'd have said "it allows us to use the this keyword with function chaining". My argument is that JS has always been an object-oriented language, and using the this keyword would close the gap between methods and functions even more. Those custom pipeline-able functions from your examples would hardly look different, only they'd get even more concise without needing to declare the implicit argument.

@gilbert
Copy link
Collaborator

gilbert commented Dec 7, 2015

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

@gilbert
Copy link
Collaborator

gilbert commented Dec 7, 2015

@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

@barneycarroll
Copy link

You're using this for that? :0

@spion
Copy link
Author

spion commented Dec 7, 2015

@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)
  }
}

@gilbert
Copy link
Collaborator

gilbert commented Dec 7, 2015

Your "pipe operator version" of papp is a bit of a strawman; I don't think anyone suggested defining or using such a function.

Well the main point of the |> operator was to code in a point-free-ish style. If you're going to go as far as those examples, you may as well extend the language:

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?

@spion
Copy link
Author

spion commented Dec 7, 2015

As I said, the main point of bind is precisely to avoid attaching things to the built in prototypes. Its full of pitfalls. For example, node's Stream class defines pipe which will override Object.prototype.pipe behaviour.

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 bind will be rejected as superfluous, even though its not - its precisely the missing link that explains how JS this works, gets rid of the need to attach stuff to existing prototypes or even to use mixins (which are so error prone with their potential name clashes and overrides!)

@gilbert
Copy link
Collaborator

gilbert commented Dec 7, 2015

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 bindPipe (or some other name) were standardized, no new syntax would be required, no?

@RangerMauve
Copy link

@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 bindPipe might get standardized is very weak since you're more likely going to come up with a fancy use case that isn't going to be standardized any time soon, or the standardized version is going to end up being in conflict with whatever you thought up initially. Not extending native prototypes is an established best practice, so I don't think it makes sense to be arguing for it or using it in examples to back whatever point you're making.

@gilbert
Copy link
Collaborator

gilbert commented Dec 7, 2015

Not extending native prototypes is an established best practice, so I don't think it makes sense to be arguing for it or using it in examples to back whatever point you're making.

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 Object.prototype.bindPipe is accepted into the language, just like how Array.prototype.find was accepted into ES6. Would either the |> operator or the :: operator provide any significant gain over this bindPipe method?

@RangerMauve
Copy link

@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 pipe function for pipelines, but I could see people's excitement over having that as a language feature.

@barneycarroll
Copy link

Isn't the most obvious benefit of the bind operator that defining new prototypes becomes redundant? pipe can exist as a simple function and be invoked on any entity. In contrast, the example wouldn't work with a String literal, which doesn't inherit from Object.

@gilbert
Copy link
Collaborator

gilbert commented Dec 7, 2015

the two added syntaxes would allow for custom chainable functions

Are you referring to custom functions that wouldn't be possible to use with bindPipe, but would with a new syntax ? If so, I would like to see them – it is the question I'm asking, for argument's sake.

@barneycarroll Actually a string literal was the first example I tried it with. It seems to work on my end..

@RangerMauve
Copy link

@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 Object.prototype is a pretty huge breaking change.

@spion
Copy link
Author

spion commented Dec 7, 2015

The biggest problem with defining anything new on Object.prototype is the use of objects as dictionaries. The last addition (__proto__) was widely considered a mistake, so I don't see that its likely that anything else is added.

@barneycarroll
Copy link

@spion FWIW Javascript has given us a multitude of ways of avoiding this problem space in recent years:

  • ES5's Object.create( {{literal object expression}}, null ) allows us to create a keyed object without a prototype
  • ES7's Dict proposal offers idiomatic sugar for achieving the same
  • ES6's Map allows for keyed entities with explicit get / set / has access methods that eliminate the problem of confusing keys with properties

@spion
Copy link
Author

spion commented Dec 7, 2015

@barneycarroll then are there any other reasons why __proto__ deprecated in favor of Object.getPrototypeOf and Object.setPrototypeOf ?

@barneycarroll
Copy link

AFAIK __proto__ is an ugly kludge that was never part of the standards track.

@spion
Copy link
Author

spion commented Dec 7, 2015

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

@texastoland
Copy link

  1. These proposals do appear to compete. (::) actually looks more natural for closures (aside from aforementioned arguments) since you just name your unary argument _ (may as well be this) by convention.

  2. It seems trivial to write a function today just as cleanly e.g.

    const pipe = (x, ...fs) => fs.reduce((y, f) => f(y), x)
    const add = (x, y) => x + y
    const double = x => add(x, x)
    pipe(25, double, _ => add(7, _)) // == 57
  3. I believe your proposal does shed light on more interesting problems. For starters, most of your examples manually curry functions. I've been researching functional libraries and every one reimplements auto-currying slightly differently. Unlike (|>), it's quite non-trivial and more oft implemented. I'm biased however, because your proposal inspired me to undertake that one soon.

  4. Your proposal (but not (::)) would become trivial if we could implement binary operators:

    infixl 9 |>(x, f) {return f(x)}
    // </pseudocode>
    const add = (x, y) => x + y
    const double = x => add(x, x)
    25 |> double |> _ => add(7, _) // == 57

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.

@ghost
Copy link

ghost commented Mar 6, 2016

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 @util code is needed to achieve the same functionality (except for the currying).

Demo @ http://bit.ly/1QVIrbx
// hit Compile, then Eval, then check the console (F12)

// -------------------------------------------------
// 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
}

@spion
Copy link
Author

spion commented Mar 6, 2016

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? :)

@benlesh
Copy link

benlesh commented Mar 6, 2016

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.

@ghost
Copy link

ghost commented Mar 6, 2016

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

  • better usability for method extraction + this-binding
  • better usability for Function.prototype.call
  • don’t have to worry about .bind or .call being shadowed/mutated
  • addresses some super use cases without need for implicit lexical binding
  • static syntax means no dynamic tests (e.g. for mutation of .bind or .call) ⇒ better performance
  • important for code generators that don’t want to be penalized for generating .call

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.

@benlesh
Copy link

benlesh commented Mar 7, 2016

I think i would never use bind operator.

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.

@ghost
Copy link

ghost commented Mar 7, 2016

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 😄

@benlesh
Copy link

benlesh commented Mar 7, 2016

@thelambdaparty I'm for that too.

@AlexGalays
Copy link

I loathe prototypes, class, this, bind (and yes, I now precisely how they work), so to me, |> is much better than ::.

@littledan
Copy link
Member

The intention of this proposal is to largely subsume the bind operator.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 24, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants