jashkenas / coffeescript

Unfancy JavaScript
https://coffeescript.org/
MIT License
16.49k stars 1.99k forks source link

Infix function/method application #1429

Closed alvivi closed 10 years ago

alvivi commented 13 years ago

It will be nice to have infix function/method application in coffeescript, just like Haskell does. This would improve the look of EDSLs in coffeescript. Here is an example of infix function application:

dividedBy = (x, y) -> x / y

dividedBy 10, 2   # dividedBy(10, 2);
10 `dividedBy` 2 # dividedBy(10, 2);

Infix method application could be implemented like this:

class A
    constructor: (@x) ->
    add: (a) -> new A @x + a.x
    addAndMultBy: (a, m) -> new A (@x + a.x) * m.x

a = new A 1
b = new A 2
m = new A 10

a `.add` b # a.add(b);
b `a.addAndMultBy` m # a.addAndMultBy(b, m);

Haskell also allows infix expressions like a 'foo x + 1, y' b # foo(x + 1,y,a,b); and function operators are not restricted by its arity a 'foo' b, x, y # foo(a,b,x,y).

benekastah commented 13 years ago

This is something I like from haskell as well. I thought the choice of using the backtick was a little funky, though. It's an awfully hard character to reach for such a useful syntax. That particular character is already taken in coffeescript anyway. Also I didn't understand why the function had to be wrapped in two backticks, seeing as the function name would be recognizable as such without having to be wrapped in anything. If we did this in coffeescript, I think it would be nice to smooth over those implementation details. Maybe something silly like prefixing the function with the % sign?

gt = (x, y) -> x > y

5 %gt 6
michaelficarra commented 13 years ago

% would be ambiguous in most cases, one of those being your own example:

$ coffee -bep '5 %gt 6'
5 % gt(6);
weskerfoot commented 12 years ago

I was thinking about this today, and I figured it might be possible to distinguish between prefix and infix by only allowing infix functions to be used in an infixy way, but then the problem is how do you distinguish between the two? I like benekastah's idea of prefixing it with something, but the question is what? We're kind of restricted by the ASCII charset.

robotlolita commented 12 years ago

I +1 this request, as long as one can find a nice syntax that isn't already taken by the language, or that doesn't make the parser even more ambiguous.

PS.: As I was reading that page, the -: <expression> :- combo seems pretty okay to me, given you can't have hyphens as part of identifiers, and the subtraction operator can't act as a post-fix (comming before he colon in an object literal).

The grammar could be:

<expression> ::= <value> | <invocation> | <code> | <operation> | ( ... )
<operation> ::= <unary> | <prefix> | <postfix> | <existential> | <infix> | ( ... )
<infix> ::= "-:" <expression> ":-"

Of course, this is not as lightweight as one could wish for. If infix expressions (those that evaluate to a function that can be applied to the lhs and rhs), we could just have -: <identifier as a way of making a function object an infix.

I'm not too keen on the infix notation for method invocation (the a -:.method:- b example) since method invocation are dispatched on a single objects. Applying a function object coming that's stored in an object as an infix would be okay — the infix expression could be used for that.

All in all, I think this would really make CoffeeScript a lot more expressive :3

shesek commented 12 years ago

+∞. Pinging @jashkenas - any chance to get your feedback on that?

Also, just a thought: what about allowing (pre|suff)ix-less special character operators? Its problematic because JavaScript only allows alpha-numerics and $/_ in identifiers, but maybe it can be done using some "magic object" to hold the operators as properties - e.g. OPERATORS = '**': Math.pow; 123 ** 456 -> OPERATORS['**'](123, 456)? I don't think using a magic object is a great idea (quite awful, really)... but I would really love to have that feature.

jashkenas commented 12 years ago

I dunno. Why would you want to limit yourself to only having one of the arguments on the LHS? Why would this be undesirable?

a, b `infix` c, d

Probably, in most cases where you have actual objects, you won't need this in any case:

a.dividedBy b

All of the examples in this thread are for operators ... that are already real operators. How about some examples of how this would be helpful for real code? In the meantime, closing as a wontfix.

robotlolita commented 12 years ago

@jashkenas Well, it's not about operators per-se but the readability (natural flow, if you will). You're right that objects solve part of this problem, but it's not really practical to have all operations on objects, all the more given the high use of functional idioms in JavaScript (and I believe more so in CoffeeScript, from people not coming from strict OO-only languages).

So, with objects you have a single dispatching where the single item on the LHS is the most important thing in the equation, and this is still restrictive. For example:

var slice = () -> [].slice.call.apply [].slice, arguments
var sequence = { 0: 1, 1: 2, length: 2 }

With usual message passing for OO idioms, this looks okay

sequence.__proto__ = Array.prototype
sequence.slice 1 # => [2]

With the usual function application idioms, the application looks awkward

slice sequence, 1 # => [2]

With the ability to define any function as infix, you can get the same natural reading from idiomatic OO dispatching, but without the need to monkey-patch the object (so, less overhead and a lot safer)

sequence `slice` 1 # => [2]

This is all good for the usual single-dispatching from OO-ish idioms. But indeed functional idioms allow people to do multiple dispatching, as it's not apparent who are the main players in a function application. Magpie solves this nicely by allowing you to define arbitrary number of arguments to come before the function's identifier, and arbitrary to come after the function's identifier.

def (this is Person, action is Verb) to (predicate is String)
  print("Let it be know that " + this name + action + " to " + predicate)
end

Alan went to "the church"
# => "Let it be know that Alan went to the church"

( You can read more about Magpie's handling of function application here: http://magpie.stuffwithstuff.com/calls.html )

goto-bus-stop commented 12 years ago

...just some possible infix application.

# string comparison
like = (strOne, strTwo) -> strOne.toLowerCase() is strTwo.toLowerCase()
a = 'some string'
b = 'SOME string'

if a `like` b then do_something()

# something more useful, class implement
implements = (base, props) -> base::[key] = value for value, key in props
class UsefulStuff
  method1: (does) -> things()
  method2: (is) -> @extremely_useful

someProps =
  method3: (also) -> a_neat method
  importantNumber: 123

UsefulStuff `implements` someProps

(new UsefulStuff).importantNumber is 123 # true

...becomes...

// comparison
if(like(a, b)) do_something();

// class
function UsefulStuff(){ /*etc*/ }
implements(UsefulStuff, someProps);

In my humble opinion, base 'implements' properties (apparently backticks are not to be escaped) looks nicer/cleaner than implements base, properties...

conradz commented 12 years ago

@jashkenas I'm not familiar with the Haskell style, but it seems similar to the F# pipelining operator. Its really useful for stuff like filtering, sorting, etc. on a collection/array. Here's one example of how this is used in F#:

Dealer.cards()
    |> Seq.map (fun c -> (rand.Next(), c))
    |> Seq.sortBy fst
    |> Seq.map snd
    |> Seq.take 10
    |> Seq.toList

This converts the sequence of cards into a tuple containing the card and a random number, sorts by the random number, gets the second element of each tuple (converting it back into a sequence of cards), gets the first 10 cards, and converts it to a list.

It's equivalent to the following F# code:

Seq.toList
    (Seq.take 10
        (Seq.map snd
            (Seq.sortBy fst
                (Seq.map (fun c -> (rand.Next(), c))
                    Dealer.cards()))))

As you can see, the first way is much more readable.

(In case you get confused when reading this code, F# does not need to seperate the arguments for functions.)

shesek commented 12 years ago

How about some examples of how this would be helpful for real code

Why I personally find this feature very exciting:

  1. It would make DSL in CoffeeScript even more awesome. Using domain-specific operators is a really elegant way of doing stuff that don't really belong in any "class" hierarchy, somewhat as an alternative to mixins. It is really more of a personal choice - while this can be achieved in most cases as object methods, I find myself slowly straying away from OOP-style code into functional-style code, and having this feature would greatly benefit me.
  2. Its very helpful for cases where the LHS is a general object (strings, arrays, etc) and you don't want to pollute the native prototypes.
  3. Those operators allows the consumer of an object to use fluent-style programming on it without the object developer having to do anything. It puts the control in the hands of the object user, rather than the creator.
  4. Allowing (pre|suff)ix-less special-characters operators, as I suggested above, would let developers customize CoffeeScript's syntax to their needs with great ease and without modifying the core code of CS. Some examples of general-purpose operators that I would want to add:
    • Extending objects: obj ++ { a: 1, b: 2 } (would work like _.extend())
    • Prototypal inheritance: parent <| { a: 1, b: 2 } (as suggested for ES.next)
    • Regex matching: str =~ /foo/ (-> str.match /foo/)
    • Mapping arrays: [1,2,3] |> (n)->n+1 (-> [1,2,3].map .... Yeah, the |> operator is kinda confusing as it looks similar to <| but has nothing to do with. I could probably find a better one for that)
    • ... Special operators for composing functions, currying, array forEach, extra mathematical operators, binding functions to objects... there are endless possibilities with this, and its completely under the control of the developer.

IMHO having that feature, in combination with CoffeeScript's syntax, would make CoffeeScript one of the best languages (that I know of) for DSLs, fluent-style/literal programming and customization by the developer.

redexp commented 11 years ago

Very sad that this issue is 2 years old and still not implemented. One more really regular usage is extend objects

options = ^defaults extendWith options or {}

It's so beautiful! And here my proposition on syntax - it's ^ before first parameter. It's like a note which indicates that here goes infix expression. And also ^ not used in coffee.

Here above example with inline object instead of defaults

options = ^(
  data: "id=1"
  url: "http://"
)  extendWith options or {}

I think full syntax for infix should be ^(...) functionOrMethodName arg2, arg3, .... Brackets can be avoided if in it not some expression.

So here valid examples

^variable functionName param1, param2
^functionName(params) functionName param1, param2
^object.field functionName param1, param2
^object.method(params) functionName param1, param2
^(variable + 1) functionName param1, param2
^(
  field1: 1
  field2: 2
)  functionName param1, param2
redexp commented 11 years ago

Sorry, I forgot about xor operator ^, maybe instead of it we can use :

options = :defaults extendWith options or {}
xixixao commented 10 years ago

@michaelficarra Care to share why reopened - still applicable?

The examples here seem to come from land of custom DSLs, and I think that CoffeeScript can be a target for these but probably shouldn't force them onto the general public. This goes along requests for macros, operator overloading etc. - with great power comes great mess (C).

michaelficarra commented 10 years ago

@xixixao: I'm not sure why we re-opened it. I am personally in favour of the proposal. But you're right, this just wouldn't be consistent with other, similar design decisions we've made.

jashkenas commented 10 years ago

Then let's put it down for now.