Infix Operators (Re: March 22/23 notes)

Claus Reinke claus.reinke at talk21.com
Fri Mar 25 02:53:34 PDT 2011


> It strikes me as odd to see specific infix operators hardcoded
> in the language spec.   ..
> I'd be interested to learn whether user-defined infix operators
> have been considered, to keep the language-level specification
> limited but general, while allowing late specification of details
> in library code.

It is interesting to note that Javascript already has (at least) two
ways of defining infix operations, so one might wonder why
neither of them is considered good enough to avoid building
specific infix operators into the language spec.

Variant A (Lisp heritage): define your own application function,
    giving you a meta-level on which you control fixity

Variant B (Smalltalk heritage): every object has its own parser
    for the messages it receives, and method selectors are like
    infix operators (similar to todays "fluent" APIs)

(implementation sketches for both variants appended below)

In Javascript, both variants are handicapped by excessive syntax:
in particular, parenthesis are cemented into language constructs
instead of being applied freely, for grouping only; one negative
consequence is that parens cannot be omitted when not needed,
another is that additional syntax (commas as separators) is used
when simple grouping is intended (commas also cannot be
omitted when not needed).

If Javascript could be simplified, to use parens for grouping only,
many unnecessary parens and commas could be omitted (even
more so if future Javascript versions have predictable function
arities for the majority of functions), making user-defined infix
operators in either style more palatable. It would also open up
the possibility to introduce operator sections (to convert existing
infix operators from syntax fragments to first-class prefix functions).

In other words, removing obstacles to user-defined infix
operators (making the language simpler, but more general)
might be an alternative to hardwiring more of them into the
language spec (making the language more complex).

The functionality of the operators in question still needs
to be added, but they could be (pre-defined) functions in
a standard library, instead of more language syntax.

Claus

// emulating infix operators (implementation sketch)

// ---------- Variant A: Lisp heritage

// straightforward infix application;
// so we can write _(x ,infixop, y)
function _(x,op,y) { return op(x,y); }

// ---------- Variant B:Smalltalk heritage

// combining wrapper with traditional method chaining;
// so we can write $(x).infixop(y)
//
// (we can define a whole bunch of infix ops in one wrapper)
function infix(ops) {
  function $$(x){ this.x = x; }
  for (var op in ops) {
    if (ops.hasOwnProperty(op))
      $$.prototype[op] = (function(op) {
                            return function (y) { return 
ops[op](this.x,y); };
                          }(op));
  }
  return function(x) { return new $$(x); }
}

// ---------- testing

function log(msg) {
  if (typeof console!=='undefined')
    console.log(msg);
  else if (typeof WScript!=='undefined')
    WScript.Stdout.WriteLine(msg);
}

// neither style support symbolic ops, and wrapping infix to prefix
// is syntactically expensive, compared to Haskell's sections (+):
//
//  #(x,y) { x+y }
//  function(x,y) { return x+y; }
//
//  so expensive, in fact, that we'll probably wrap each infix op
//  once, to avoid having to repeat the wrapping; that means finding
//  another name for each infix op, and adding it to the current scope

function gt(x,y) { return x>y; } // wrap once, to avoid repetition

// if infix ops are first-class functions, flipping arguments is simple
// (if only we had easy currying and partial application, though..)
function flip(f) { return function(y,x) { return f(x,y); }; }

// define some infix ops
var $ = infix({max: Math.max, min: Math.min, gt: gt });

// variant A:
// lots of parens and commas, no precedence, no symbolic ops
log( _(10, Math.max, 3) );
log( _(10, Math.min, 3) );
log( _("hello", gt, "world") );
log( _("world", gt, "hello") );
log( _("hello", flip(gt), "world") );

// variant B:
// lots of syntax noise, no precedence, ops aren't first-class,
// no symbolic ops
log( $(10).max(3) );
log( $(10).min(3) );
log( $("hello").gt("world") );
log( $("world").gt("hello") );

// variant A could add operator precedence, parsing-style (use longer
// argument lists, then resolve precedence as if the parameters were
// tokens in a parser); could something similar work for variant B?
 



More information about the es-discuss mailing list