Wait...this got me thinking... The proposal itself doesn't bring along a
lot of merits, but it seems like it could be a great stepping stone to a
limited pattern matching syntax. This would probably be a little more
justifiable IMHO than merely a custom destructuring syntax. Maybe something
like this:

```js
Type[Symbol.pattern] = (obj) => {
  return [obj.a, obj.b];
}

const Other = {
  [Symbol.pattern]: obj => obj,
}

class None {
  static [Symbol.pattern](obj) {
    return obj
  }
}

// Pattern matching, signaled by `in` here
switch (object) in {
  case Type([a, b]): return a + b
  case Other({a, b}): return a * b
  case None: return undefined // Special case, no identifier initialized
}

// Extensible destructuring, easy to implement with the pattern
// matching
let Type([a, b]) = object
let Other({a, b}) = object
```

In the destructuring phase for both, I was thinking about the following
semantics to assert the type, based on `typeof` and the prototype. This
will help engines in optimizing this as well as some type safety for all of
us.

```js
function _checkProto(object, Type) {
  // Note: Type[Symbol.pattern] must be callable
  if (typeof Type[Symbol.pattern] !== 'function') throw new TypeError()
  if (typeof Type === 'function') {
    if (type === Array) {
      return Array.isArray(object)
    } else {
      return object instanceof Type
    }
  } else {
    return Object.prototype.isPrototypeOf.call(Type, object)
  }
}

function isInstance(object, Type) {
  switch (typeof object) {
    case 'object': return obj != null && _checkProto(object, Type)
    case 'function': return Type === Function
    case 'boolean': return Type === Boolean
    case 'number': return Type === Number
    case 'string': return Type === String
    case 'symbol': return Type === Symbol
    case 'undefined': return false
  }
}
```

Finally, get the result and do a basic variable pattern assignment, LHS
being the operand, and RHS calling `Type[Symbol.pattern]`.

The `switch` statement example would (roughly) desugar to the following:

```js
switch (true) {
  case isInstance(object, Type):
    let [a, b] = Type[Symbol.pattern](object)
    return a + b

  case isInstance(object, Other):
    let {a, b} = Other[Symbol.pattern](object)
    return a * b

  case isInstance(object, None):
    return undefined
}
```

The destructuring examples would (roughly) desugar to this:

```js
if (!isInstance(object, Type)) throw new TypeError()
let [a, b] = Type[Symbol.pattern](object)
if (!isInstance(object, Other)) throw new TypeError()
let {a, b} = Other[Symbol.pattern](object)
```

The type assertions will help engines in optimizing this, and it'll also
make this safer. It also just makes sense for pattern matching.

As a side effect, you can get the value without destructuring (i.e. the
literal result of `Symbol.pattern`) via this:

```js
let Map(m) = someMap
let m = Map[Symbol.pattern](someMap)
```

I, myself, came up with a Scala-inspired case class concept
<https://gist.github.com/impinball/add0b0645ce74214f5aa> based on this, and
used it to make try-catch handling a little more Promise-like, which I know
quite a few TypeScript users would eat up in a hurry
<https://github.com/Microsoft/TypeScript/issues/186> (particularly the sum
type implementation). I also created a little toy Option/Maybe
implementation <https://gist.github.com/impinball/4833dc420b60ad0aca73>
using pattern matching to ease `null`/`undefined` handling.

And also, check out my gist
<https://gist.github.com/impinball/62ac17d8fa9a20b4d73d> for a proposed
`Symbol.pattern` prolyfill for the primitive types. That would allow for
things like this:

```js
switch (list) in {
  case Array([a, b, c]): return doSomething(a, b, c)
  case Map({a, b, c}): return doSomethingElse(a, b, c)
  default: return addressBadType(list)
}
```

> ---------- Forwarded message ----------
> From: Andreas Rossberg <rossb...@google.com>
> To: "Samuel Hapák" <samuel.ha...@vacuumapps.com>
> Cc: es-discuss <es-discuss@mozilla.org>
> Date: Tue, 4 Aug 2015 14:26:45 +0200
> Subject: Re: Extensible destructuring proposal
> On 31 July 2015 at 20:09, Samuel Hapák <samuel.ha...@vacuumapps.com>
wrote:
>>
>> So, do you still have objections against this proposal? Could we
summarize them?
>>
>> @Andreas, do you still think that there is category error involved?
>
>
> If you want to overload existing object pattern syntax, then yes,
definitely. I strongly disagree with that. It breaks regularity and
substitutability (you cannot pass a map to where a generic object is
expected).
>
> As for a more Scala-like variant with distinguished syntax, I'm fine with
that semantically. But I still don't buy the specific motivation with maps.
Can you give a practical example where you'd want to create a map
(immutable or not) for something that is just a record of statically known
shape? And explain _why_ you need to do that? Surely it can't be
immutability, since objects can be frozen, too.
>
> To be clear, I wouldn't reject such a feature outright. In fact, in a
language with abstract data types, "views", as they are sometimes called in
literature, can be valuable. But they are also notorious for a high
complexity cost vs minor convenience they typically provide. In particular,
it is no longer to write
>
>   let {a: x, b: y} = unMap(map)
>
> as you can today, then it would be to move the transformation to the
pattern:
>
>   let Map({a: x, b: y}) = map
>
> So I encourage you to come up with more convincing examples in the
JavaScript context. Short of that, I'd argue that the complexity is not
justified, at least for the time being.
>
> /Andreas

-- 
Isiah Meadows
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to