Infix Operators (Re: March 22/23 notes)
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 opsop; }; }(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?
Interesting idea; I'd never considered lifting some of these good syntax ideas from Haskell before.
One issue with the Haskell ...
syntax directly is conflict with the quasi-literals proposal, but we can think about alternatives offline (let's not get into a discussion of concrete syntax here on the list -- those never go well here).
Also, in principle I like the idea of borrowing Haskell's partial operator application syntax, but it doesn't work with operators like + and -, since those are also already valid unary prefix operators. We could think about a somewhat more explicit syntax, where you could represent partial operator application with some explicit token representing a curried argument position. But then this wouldn't be quite as lightweight and lovely as Haskell's syntax, and it would require stealing another ASCII special character, and at that point it's not sure whether the benefit would outweigh the cost.
But speaking for myself anyway, I'll chew on the idea. Thanks for bringing it up!
Best,
Interesting idea; I'd never considered lifting some of these good syntax ideas from Haskell before.
thanks. Yes, I've often noticed that people equate Haskell language design with its advanced type system ideas, while the much more stable ideas (such as reducing syntactic noise) in the base language tend to get overlooked.
Yet the quality of the base language design is a major contributing factor in making advanced design ideas thinkable and implementable, without being afraid of getting mired in unrelated issues.
One issue with the Haskell
...
syntax directly is conflict with the quasi-literals proposal, but we can think about alternatives offline (let's not get into a discussion of concrete syntax here on the list -- those never go well here).
Thanks for elaborating - the first comments that said "not on this list, please" without explaining why were confusing to me!-) Perhaps the nature/acceptable levels of suggestions for this list could be hinted at on the list summary page?
At this stage, I'm more concerned with the general ideas anyway: solving infix ops once and for all, instead of one by one, and making sure that conversions between infix and prefix are supported.
What that looks like in Javascript may be quite different from what it looks like in Haskell. The solution has to fit the language.
Also, in principle I like the idea of borrowing Haskell's partial operator application syntax, but it doesn't work with operators like + and -, since those are also already valid unary prefix operators.
Haskell happens to have a unary prefix minus, and using the same symbol for infix and prefix op has turned out to be a well-meant, but troublesome idea..
#-functions might make inline functions nearly as light- weight as in Haskell, it is just worth keeping in mind that a functional language has a lot of infrastructure that expects (prefix/first-class) functions, so if infix ops are a piece of syntax, there's going to be a compatibility issue.
But speaking for myself anyway, I'll chew on the idea. Thanks for bringing it up!
Thanks for listening, and letting me know!-)
Claus
Is there someplace where one could look up what alternative design options were considered and discarded for Ecmascript in the past?
It strikes me as odd to see specific infix operators hardcoded in the language spec. The discussion notes (repeated below) confirm that this is a risky approach, but it is also a limiting approach (you can't have every application domain lobbying the language committee for new infix operators).
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.
For comparison, Haskell has taken a simplistic, but usually good enough approach, permitting simple forms of user- defined infix operators, thereby keeping operator-specific decisions out of the language spec (these become a library responsibility, and libraries tend to be easier to change - at least, alternative definitions can be provided if the standard library operator definitions turn out to be flawed, and new applications aren't dependent on language evolution):
no a priori distinction between infix and other functions
rule of thumb:
support for conversion betwen infix and prefix:
f
turns prefix function f into infix operatoroperator precedence and associativity can be defined for both synbolic and backquoted operators
There are more details, e.g., indicating constructors, or permitting "sections" (a section is an infix operator, partially applied to one of its arguments, such as (1/) or (/2); with no general support for partial application in Javascript, operator sections seem not applicable here, but they are handy for higher-order functions, such as map, filter, ..).
So you can write "(3
div
2)", or "map (+1) [1,2,3]". Without operator sections, "map (+1)" would become "map (\x->x+1)".And all of that is defined in libraries, so if users want to define addition over arrays or matrices, or new infix operators for dealing with asynchronous code and callbacks, they can (similarly, if they want alternative operators with reversed order of arguments, or if the standard behaviour is inadequate for their application domain).
Limitations have been discussed, too (there are ten fixed operator precedence levels, where some would prefer relative operator precedences, or at least more levels). On the whole, the system has proven to be quite useable.
Claus "Give a man an infix operator and you feed him for a day. Teach a man to make infix operators and you feed him for a lifetime"
PS. I hope it is okay to contrast Ecmascript design questions against Haskell's here?