proposal-record-tuple icon indicating copy to clipboard operation
proposal-record-tuple copied to clipboard

how to determine whether an unknown variable is a specific Record / Tuple

Open eczn opened this issue 1 year ago • 3 comments

let p = #{ x: 11, y: 22 }

function handlePoint(x: unknown) {
  if (x instanceof /* ??? */) {  // how to determine whether an unknown variable is a specific Record / Tuple
    console.log(`x is a point:`, x)
  } else {
    throw new Error(`x is not a point`)
  }
}

try to name #{ x, y }:

record Point #{ x, y }
const p = Point #{ x: 1, y: 2 }
x instanceof Point

or:

x instanceof #{x, y} 

eczn avatar Apr 18 '24 09:04 eczn

Records are anonymous primitive compound value, use a structural check (test fields and types) instead of a nominal check (instanceof). They are closer to string values than class instances.

Your example would be:

function handlePoint(x) {
  if (isPoint(x)) {
    console.log(`x is a point:`, x)
  } else {
    throw new Error(`x is not a point`)
  }
}

function isPoint(x) {
  return typeof x === "record" && typeof x.x === "number" && typeof x.y === "number"
}

There are many runtime or compile time libraries to help with type checking, this is out of scope for this proposal in my opinion. The Pattern Matching proposal may be a better place to discuss such feature.

demurgos avatar May 06 '24 19:05 demurgos

Having an "is same structure" predicate could be interesting, but the exact semantics would be tricky.

For example, taking #{foo: #[ 1, false ] } as an example.

First, should such a predicate recurse into values that are R/T? Aka, should it check the structure of .foo? Then if it does, what should it do regarding non R/T values? Should it compare them by value, by type, or not at all? If not at all, any tuple of length 2 for foo would pass. If by type, it's need to be a tuple of [number, boolean]. If by value, should there be a difference if the value is forgeable (string, number, bool, null?, registered symbol) or unforgeable (unique symbol, objects if they're allowed).

Comparing structure deeply by type of the leaf values might be a good compromise. Then you could implement the isPoint above simply as const isPoint = x => isSameStructure(#{ x: 0, y: 0 })

mhofman avatar May 07 '24 18:05 mhofman

Comparing structure deeply by type of the leaf values might be a good compromise.

This is still type checking, but the type is defined with a "template" value. This feels like a needless extra indirection when you could have const isPoint = x => isType(x, RecordType({x: NumberType, y: NumberType})). A downside of this indirection is that it forces dummy values (0 in your example) and prevents finer type constraints (literals, unions, etc.)

The main issue is what you described: the semantics are not clear cut. Answering the semantics means defining a type system for JavaScript (beyond typeof or instanceof).

TC39 is very far from being ready to standardize on a type system that can be checked IMO. Type Annotations is related when going in this direction. Reserving type syntax for custom use means that a standard type system would have to coexist with it, making it a bit harder to get.

demurgos avatar May 12 '24 21:05 demurgos