Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Swift-style enum with associated values in Typescript? [duplicate]

Unfortunately, as of 0.9.5, TypeScript doesn't (yet) have algebraic data types (union types) and pattern matching (to destructure them). What's more, it doesn't even support instanceof on interfaces. Which pattern do you use to emulate these language features with maximal type safety and minimal boilerplate code?

like image 406
thSoft Avatar asked Jan 10 '14 00:01

thSoft


People also ask

Can associated values and raw values coexist in Swift enumeration?

The Problem with Associated Values We had to do this because Swift doesn't allow us to have both: raw values and associated values within the same enum. A Swift enum can either have raw values or associated values.

What is associated type enum Swift?

In Swift enum, we learned how to define a data type that has a fixed set of related values. However, sometimes we may want to attach additional information to enum values. These additional information attached to enum values are called associated values.

Can you iterate through an enum TypeScript?

Use the Object. keys() or Object. values() methods to get an array of the enum's keys or values.

How do I get all the enum values in TypeScript?

To get all enum values as an array, pass the enum to the Object. values() method, e.g. const values = Object. values(StringEnum) . The Object.


4 Answers

TypeScript 1.4 adds union types and type guards.

like image 176
thSoft Avatar answered Oct 20 '22 02:10

thSoft


Example to illustrate the accepted answer:

enum ActionType { AddItem, RemoveItem, UpdateItem }
type Action =
    {type: ActionType.AddItem, content: string} |
    {type: ActionType.RemoveItem, index: number} |
    {type: ActionType.UpdateItem, index: number, content: string}

function dispatch(action: Action) {
    switch(action.type) {
    case ActionType.AddItem:
        // now TypeScript knows that "action" has only "content" but not "index"
        console.log(action.content);
        break;
    case ActionType.RemoveItem:
        // now TypeScript knows that "action" has only "index" but not "content"
        console.log(action.index);
        break;
    default:
    }
}
like image 28
Franklin Yu Avatar answered Oct 20 '22 03:10

Franklin Yu


Here's an alternative to the very good answer by @thSoft. On the plus side, this alternative

  1. has potential interoperability with raw javascript objects on the form { type : string } & T, where the shape of T depends on the value of type,
  2. has substantially less per-choice boilerplate;

on the negative side

  1. does not enforce statically that you match all cases,
  2. does not distinguish between different ADTs.

It looks like this:

// One-time boilerplate, used by all cases. 

interface Maybe<T> { value : T }
interface Matcher<T> { (union : Union) : Maybe<T> }

interface Union { type : string }

class Case<T> {
  name : string;
  constructor(name: string) {
    this.name = name;
  }
  _ = (data: T) => ( <Union>({ type : this.name, data : data }) )
  $ =
    <U>(f:(t:T) => U) => (union : Union) =>
        union.type === this.name
          ? { value : f((<any>union).data) }
          : null
}

function match<T>(union : Union, destructors : Matcher<T> [], t : T = null)
{
  for (const destructor of destructors) {
    const option = destructor(union);
    if (option)
      return option.value;
  }
  return t;
}

function any<T>(f:() => T) : Matcher<T> {
  return x => ({ value : f() });
}

// Usage. Define cases.

const A = new Case<number>("A");
const B = new Case<string>("B");

// Construct values.

const a = A._(0);
const b = B._("foo");

// Destruct values.

function f(union : Union) {
  match(union, [
    A.$(x => console.log(`A : ${x}`))
  , B.$(y => console.log(`B : ${y}`))
  , any (() => console.log(`default case`))
  ])
}

f(a);
f(b);
f(<any>{});
like image 37
Søren Debois Avatar answered Oct 20 '22 04:10

Søren Debois


To answer

it doesn't even support instanceof on interfaces.

Reason is type erasure. Interfaces are a compile type construct only and don't have any runtime implications. However you can use instanceof on classes e.g. :

class Foo{}
var x = new Foo();
console.log(x instanceof Foo); // true
like image 35
basarat Avatar answered Oct 20 '22 03:10

basarat