Skip to content

Latest commit

 

History

History
234 lines (180 loc) · 5.06 KB

design.md

File metadata and controls

234 lines (180 loc) · 5.06 KB

Design

This proposal introduce :: (double colon) infix notation which the left side is an expression and the right side could be either a simple identifier or two identifiers separated by : (single colon). :: has the same precedence of . (member access) operator.

This proposal also introduce a new global Extension function object and a new well-known symbol Symbol.extension.

x::name (ad-hoc extension methods and accessors)

Usage

Declare a ad-hoc extension method and use it:

const ::last = function () {
	return this[this.length - 1]
}

[1, 2, 3]::last() // 3

Like normal const declarations, ad-hoc extension methods are also lexical:

let x = 'x'

const ::foo = function () { return `${this}@outer` }

x::foo() // 'x@outer'

{
	// TDZ here
	// x::foo()

	const ::foo = function () { return `${this}@inner` }

	x::foo() // 'x@inner'
}

Ad-hoc extension accessors:

const ::last = {
	get() { return this[this.length - 1] },
	set(v) { this[this.length - 1] = v },
}

let x = [1, 2, 3]
x::last // 3
x::last = 10
x // [1, 2, 10]
x::last++
x // [1, 2, 11]

a::b have the same precedence of a.b, so they can be chained seamlessly:

document.querySelectorAll('.note')
	::filter(x => x.textContent.include(keyword))
	.map()

Transpile

// declare a ad-hoc extension method/accessor
const ::method = func
const ::prop = {get: getter, set: setter}

// use ad-hoc extension methods and accessors
x::method(...args)
x::prop
x::prop = value

could be transpile to

const $method = CreateExtMethod(func)
const $prop = CreateExtMethod({get: getter, set: setter})

CallExtMethod($method, x, args)  // Call(func, x, args)
CallExtGetter($prop, x)          // Call(getter, x)
CallExtSetter($prop, x, value)   // Call(setter, x, [value])

See experimental implementation for details.

x::ext:name (extension)

x::O:method(...args)
x::O:prop
x::O:prop = value

roughly equals to

Call(GetOwnProperty(O.prototype, 'prop').value, x, args)
Call(GetOwnProperty(O.prototype, 'prop').get, x)
Call(GetOwnProperty(O.prototype, 'prop').set, x, [value])

If O is not a constructor, x::O:func(...args) roughly equals to O.func(x, ...args).

The behavior can also be customized by Symbol.extension.

See experimental implementation for details.

Extension API

class Extension {
	constructor(extensionMethods)
	static from(object)
}

See experimental implementation for details.

Extra features (could be split to follow-on proposals)

Declare/import multiple ad-hoc extension methods and accessors

const ::{x, y as x1} from value

work as

const $ext = Extension.from(value)
const ::x = ExtensionGetMethod($ext, 'x')
const ::x1 = ExtensionGetMethod($ext, 'y')

And

import ::{x, y as x1} from 'mod'

work as

import * as $mod from 'mod'
const ::{x, y as x1} from $mod

Static helper methods of Extension

// doesn't work
const ::cube = x => x ** 3
const ::sqrt = Math.sqrt
// work
const ::cube = function () {
	return this ** 3
}
const ::sqrt = function () {
	return Math.sqrt(this)
}
// Use Extension.method helper
const ::cube = Extension.method(x => x ** 3)
2::cube() // 8

// Use Extension.accessor helper
const ::sqrt = Extension.accessor(Math.sqrt)
9::sqrt // 3

Extension.method and Extension.accessor also accept the extra options argument.

Programmers could use "receiver" option to indicate how to deal with the receiver.

// Define ::max extension accessor
const ::max = Extension.accessor(Math.max, {receiver: 'spread'});
// spread the receiver, so `receiver::max` work as `Math.max(...receiver)`

[1, 2, 3]::max // 3

The valid values of "receiver" option:

'first' (default)

Extension.method(f, {receiver: 'first'}) behave like

function (...args) { return f(this, ...args) }

'last'

Extension.method(f, {receiver: 'last'}) behave like

function (...args) { return f(...args, this) }

'spread first' or 'first spread' or 'spread'

Extension.method(f, {receiver: 'spread first'}) behave like

function (...args) { return f(...this, ...args) }

'spread last' or 'last spread'

Extension.method(f, {receiver: 'spread last'}) behave like

function (...args) { return f(...args, ...this) }

Optional chaining

x?::extMethod()
x?::ext:method()

work as

x === null || x === undefined ? undefined : x::extMethod()
x === null || x === undefined ? undefined : x::ext:method()

Other possible features

in-place extension methods/accessors

Just like dot notation obj.prop has corresponding bracket notation obj[computedProp], we could also introduce similar syntax for extension methods/accessors.

x::[expression]

work as

const ::$extMethodOrAccessor = expression
x::$extMethodOrAccessor

Currently the author of the proposal feel this syntax do not very useful, so not include it. If there are strong use cases, we could add it back.