Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
eczn opened this issue Apr 18, 2024 · 3 comments
Open

Comments

@eczn
Copy link

eczn commented Apr 18, 2024

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} 
@demurgos
Copy link

demurgos commented May 6, 2024

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.

@mhofman
Copy link
Member

mhofman commented May 7, 2024

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 })

@demurgos
Copy link

demurgos commented May 12, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants