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

Application and binding/currying operators #1520

Closed
goomtrex opened this issue Jul 17, 2011 · 13 comments
Closed

Application and binding/currying operators #1520

goomtrex opened this issue Jul 17, 2011 · 13 comments

Comments

@goomtrex
Copy link

I've been working on a couple of operators for function application. The branch is available here.

The first operator <- applies the function on the left to the arguments on the right.

The second operator <~ is similar except it binds the function on the left and partially applies it to the arguments on the right.

Here are a couple of examples:

f <-
# f()

f <- x, y
# f(x, y)

f <~ x, y
# __curry.call(f, x, y)

o.f <~ x, y
# __curry.call(__bind(o.f, o), x, y)

The implementation of __curry is as follows:

function () {
  var f = this, xs = Array.prototype.slice.call(arguments);
  return function () {
    var ys = Array.prototype.slice.call(arguments);
    return f.apply(this, xs.concat(ys));
  }
}

The operators touch on a number of issues including:

The deprecated binding/currying operator
Indentation as application
Parameterless function calls
Avoiding parentheses

Now for the tour...

The operators are left associative:

f <- x, y <- z
# f(x, y(z))

Complex nested calls can be defined without parentheses:

a <-
  b <-
    c <- d, e <- f
    g <- h, j <- k
  l <- m, n,
    o <- p
    q <- r

###
a(
  b(
    c(d, e(f)),
    g(h, j(k))
  ),
  l(m, n,
    o(p),
    q(r)
  )
)
###

The following section from src/rewriter.coffee:

not token.generated and @tag(i - 1) isnt ',' and (tag in IMPLICIT_END or
(tag is 'INDENT' and not seenControl)) and
(tag isnt 'INDENT' or
 (@tag(i - 2) isnt 'CLASS' and @tag(i - 1) not in IMPLICIT_BLOCK and
  not ((post = @tokens[i + 1]) and post.generated and post[0] is '{')))

Can be written as:

where = (args...) ->
  args.reduce (x,y) -> x and y

either = (args...) ->
  args.reduce (x,y) -> x or y

where <-
  not token.generated
  @tag(i - 1) isnt ','
  either <-
    tag in IMPLICIT_END
    tas is 'INDENT' and not seenControl
  either <-
    tag isnt 'INDENT'
    where <-
      @tag(i - 2) isnt 'CLASS'
      @tag(i - 1) not in IMPLICIT_BLOCK
      not ((post = @tokens[i + 1]) and post.generated and post[0] is '{')

We can use the curry operator to partially apply a function:

sum = (args...) ->
  args.reduce (x, y) -> x + y

f = sum <~ 1, 2
g = f <~ [3, 4]..., 1
console.log g <- # => 11
console.log g <- 100 # => 111

The operator will bind if the given function is bound to an object:

goon = 
  status: 'drunk'
  toString: -> @status

status = goon.toString <~
console.log status <- # => 'drunk'

We can bind and curry at the same time:

xs = []
append = xs.push <~ 1, 2, 3
append 4, 5, 6
console.log xs # => [1,2,3,4,5,6]

We can bind a function to an arbitrary receiver by currying the first argument to call:

some.function.call <~ receiver

For example:

queue = new ->
    @length = 0
    @push = Array.prototype.push.call <~ @
    @pull = Array.prototype.shift.call <~ @
    this

queue.push 1, 2, 3
console.log queue.pull <- # => 1
console.log queue.pull <- # => 2
console.log queue.pull <- # => 3

The operators can be used anywhere normal function application can be used.

Post-control:

f <- if b
# f() if b

f <- for x in xs
# f() for x in xs

Expression and splat arguments:

f <- if b then 5 else 6
# f(b ? 5 : 6)

f <- 1, [2, 3, 4]..., 5
# f(1, [2, 3, 4]..., 5)

As an argument to another function:

eq f <-, g <-
# eq f(), g()

eq 5, f <- 6, 7
# eq 5, f(6,7)

Part of the motivation was to have more robust class methods:

ID = (x) -> x

class Collection
  @map = (f, coll) ->
    result = @zero <-
    @each <-
      (e, i) => @cons result, f(e, i), i
      coll
    result

class Hash extends Collection
  @zero = -> {}
  @cons = (coll, e, i) -> coll[i] = e
  @each = (f, coll) ->
    f <- e, i for i, e of coll
    undefined

# Though the implementation of @map uses @zero, @cons, and @each,
# it can be isolated and used independently:
copy = Hash.map <~ ID

original = a:1
impostor = copy original

console.log original.toString() is impostor.toString() # => true
console.log original is impostor # => false

As you can see, the operators are quite versatile. I'm currently writing a charting library so I will be able to add more tests and examples in the next week or so.

Cheers, Alex.

@michaelficarra
Copy link
Collaborator

I think a currying operator would be sweet. Not so much a function application operator, since function application is implicit anyway (except for zero-argument calls, but we have postfix-() for that already).

Also, it's been discussed many times before, but we just can't have both <- and <~ (or similarly -> and ~>) because they're too hard to quickly distinguish.

edit: Oh, though your function application operator is left-associative and the implicit function application is not. That does make the complex nested calls more readable...

@jashkenas
Copy link
Owner

For the record: We used to have <- as the currying operator, but it was removed long ago.

@geraldalewis
Copy link
Contributor

@jashkenas, it looks like it was removed after #251 -- I'm not clear on the reasoning (as you stated then, <- is identical to the bind operator, though I don't understand how [unless it worked differently than in this proposal]).

Alternate syntax for curry: <%

Mnemonic:

< = left-associative
% = partial application

append = xs.push <% 1, 2, 3

@jashkenas
Copy link
Owner

The reasoning is that call and apply are already easy enough to use, and => takes care of 90% of the common use cases for bound functions.

We removed it after auditing some real-world CoffeeScript code, and determining that it was barely ever needed.

@michaelficarra
Copy link
Collaborator

@jashkenas: I don't know how reliable an audit like that would be. I write very different code when I switch paradigms. In Haskell, a purely functional programming language, I use currying all the time. It's actually made very easy for me because any function given too few arguments produces a curried form of itself. If we had more functional programming facilities, maybe we would see an increased use of that style. But also, maybe not.

@jashkenas
Copy link
Owner

Agreed -- ubiquitous currying makes a ton of sense in a lazily-evaluated language like Haskell. For better or for worse, JavaScript is not lazily evaluated.

@goomtrex
Copy link
Author

In chronological order...

@michaelficarra

Yeah so the main use of the vanilla application operator would be indentation-as-application as mentioned above. In addition to finessing indented and parameterless calls, it does have a nice kind of symmetry with the currying operator.

I'm not too concerned about the glyphs, so feel free to suggest something else...

@jashkenas

The previous currying operator was before my time, but it looked just like a special case of apply (correct me if I'm wrong)...

With the previous operator:

fn <- receiver, a0, a1, ..., aN

With the proposed operator:

fn.call <% receiver, a0, a1, ..., aN 

@geraldalewis

I like the "spy-vs-spy" operator...

@jashkenas

Currying makes a ton of sense in any functional language, including strictly evaluated ones like ML, Scala, etc. In Scala, currying is explicit just as it would be with this proposal.

Along with bind, it kind of bridges the gap between functions that operate on this and the functional world.

Just to recap, here are some of the anticipated uses of the currying operator:

Binding i.e. currying the first argument to call:

fn.call <% receiver

Being able to use methods as normal functions:

setTimeout widget.hide <%, 1000

#  setTimeout(__bind(widget.hide, widget)), 1000);

And:

setTimeout <-
    panel.fadeOut <% 'slow',
    1000

# setTimeout(__curry.call(__bind(widget.fadeOut, widget), 'slow'), 1000);

Flexibility in defining functions:

 fold = (f, z, xs) ->
     z = f(z, x) for x in xs
     z

max = fold <% Math.max, -Infinity

As well as the class methods and indentation examples in the original post.

@goomtrex goomtrex reopened this Jul 19, 2011
@autotelicum
Copy link

Partial application with free vars can be defined - by those who want it - as:

_ = {}
partial = (func, a...) -> (b...) ->
  i = 0
  func (for arg in a
    if arg == _ then b[i++] else arg)...

# Alternative
_ = undefined
partial = (func, a...) -> (b...) ->
  b.reverse()
  func (for arg in a then arg ?= b.pop())...

# Usage:
f = (x, y, z) -> x + 2*y + 5*z
g = partial f, _, 1, _
g 3, 5 # => 30

# As the example above
fold = (f, z, xs) ->
  z = f(z, x) for x in xs
  z
max = partial fold, Math.max, -Infinity, _
max [-10..10] # => 10

# Without free vars
partial = (f, a...) -> (b...) -> f a..., b...
min = partial fold, Math.min, Infinity
show min [-10..10] # => -10

Am I missing something? Isn't this easy and elegant without new operators?
I find languages with too many operators/symbols hard to read (cf. reg.exp., perl, J).
Full code

@goomtrex
Copy link
Author

The same could be said for a number of things in CoffeeScript, for example:

loop
    ...

# vs

while true
    ...

We can also implement class extends pretty easily:

class A extends B
    ...

# vs

A = extends( B ) ->
    ...

If there was a way to define named functions (and constructors) we could dispense with the class syntax altogether.

If there's an argument for adding something to the language, I don't think it's necessarily whether it can be implemented as a library (although that needs to be considered), but whether the sugar makes certain idioms more accessible.

I think the operators are a good fit for CoffeeScript because they touch on a range of issues, including: indentation-as-application, left associative application, binding, currying, etc.

@goomtrex goomtrex reopened this Aug 28, 2011
@goomtrex
Copy link
Author

I like opening and closing doors.

@goomtrex
Copy link
Author

It's a bit quiet today, so here's an update...

The behaviour of the application operator $ proposed in #1614 Alternate Form for Parentheses has been incorporated into <-. Specifically, <- no longer closes the current call when used inline.

So this:

f <- x, y <- z

Which used to compile to this:

f(x, y)(z)

Now compiles to this:

f(x, y(z))

Which is more consistent with the (unchanged) indented syntax:

f <-
    x
    y <- z

In other news, the currying operator <~ can be applied without a receiver or arguments, resulting in a "cloned" function:

 f = (x) + 1000
 g = f <~

 f(1) is g(1)
 f isnt g

The currying operator has been pointed out as being hard to read (which is definitely true in GitHub font). Currently I'm tossing up between <~, <+, and <%. Any suggestions?

@autotelicum
Copy link

I didn't intend to say that keywords make a language more difficult to read; just operators (sigils, glyphs, special characters) and even then only if they are rarely used. What is it called in other languages? Would you have a link to some documentation from another language where its use is explained in more detail?

@GeoffreyBooth
Copy link
Collaborator

Closing as there doesn’t appear to be much interest in this proposal lately. If someone wants to submit a PR it would be considered.

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

No branches or pull requests

6 participants