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

Union/Polymorphic Types #12

Open
getify opened this issue Feb 13, 2019 · 1 comment
Open

Union/Polymorphic Types #12

getify opened this issue Feb 13, 2019 · 1 comment
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@getify
Copy link
Owner

getify commented Feb 13, 2019

Need to be able to define a union type that can include multiple types.

So... I'm imagining something like this:

var x = (union`int | string`)`42`;

Note: The ( ) are optional but useful for readability. These would work too:

union`int | string``42`;   // no space between
union`int | string` `42`;   // space between

Function Parameter Notation

Used for a function parameter:

function foo(x = union`int | string`) {
   // ..
}

empty Union Type

It's useful to combine null and undefined into a union type to allow a variable to hold either of those "empty" values:

var x = (union`undef | nul`)`null`;
x = undefined;  // OK
x = null;  // OK
x = 3;  // error!

So empty would just be a good union-type example to just build in... a type that already includes both undef and nul. It's trivial to define:

var empty = union`undef | null`;

And easy to use:

var x = empty``;
x;   // undefined
x = empty`null`;
x;   // null

This empty union type would be especially useful for "optional" annotations, as another union type, like:

function foo(x = union`int | empty`) {
   // ..
}

Union Order Matters

The order that types are listed in the union matters, for the purposes of parsing a literal and evaluating it as a type, since the processing is left to right and the first successful type evaluation "wins". In general, list types from left to right in order of most specific to most general/accepting. IOW: string, being the most liberally accepting, should be listed last, or otherwise no other types after it will ever even be evaluated for that literal.

Example:

(union`int | string`)`42`;    // 42
(union`string | int`)`42`;    // "42"

Of course, if no parsing is involved, order is irrelevant (but still a good idea to follow the above rules):

var x = 42;
(union`int | string`)`${x}`;    // 42
(union`string | int`)`${x}`;    // 42

Union Collapsing

A special case of union types is ones which should be collapsed, like number | finite, as that should just collapse to number. finite | int should collapse to finite. empty | nul should collapse to empty.

This would replace (or fit with) the "number sub-types" handling of #6.

Implementation

The way this works in runtime mode is that union() is a special tag function that produces another tag function which then runs against the subsequent template literal (for example, `hello` or `42`). The resulting tag function would allow any value that successfully passes either/any of the referenced tag functions, and error otherwise.

Rough-draft implementation:

function union([str]) {
  var typeFns = str
      .split(/\s+\|\s+/g)
      .map(s => {
        try { return Function(`return ${s.trim()};`)(); } catch (e) {}
      })
      .filter(Boolean);

  return function tag(...args) {
    var errs = [];
    for (let fn of typeFns) {
      try {
        return fn(...args);
      }
      catch (e) {
        errs.push(e.toString());
      }
    }
    
    throw new Error(`Invalid union-type value: ${errs.join(" ")}`)
  };
}

Illustrating its operation:

(union`int | string`)`hello`;  // "hello"
(union`int | string`)`42`;  // 42

(union`int | string`)`${true}`;  // error because bool is not in the union type

Polymorphic Types

The same union-types mechanism will be handle "polymorphic types" which are types that change throughout the program. There are a number of constructs that would implicitly "widen" a type to such a union type.

Example:

var a = undef``;
a = nul``;

The second assignment here will report an error (if you config it) about an unexpected assignment type, but ALSO now the type in a will be a union type of undef | nul (which might collapse to empty), since a now potentially holds either value during the program.

Another example:

var x = int`0`;
var y = string`foo`;
var z = x || y;

Again, if you config it, the x || y will produce an error about mixed operand types. But the resulting type that will be implied to z will be a union type of int | string.

And:

function foo(x = number) {
   if (x > 5) return int`${x}`;
   return "too small";
}

Error message (if config'd) about multiple return types, but also the return value in that function signature will be the union type of int | string.

@getify getify added enhancement New feature or request help wanted Extra attention is needed labels Feb 13, 2019
@AlokTakshak
Copy link

Hi Kyle
I would like to help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants