Как выразить это в Typescript?

Скажем, у меня есть интерфейс A:

interface A {
  foo: number
  bar: string
}

И у меня есть общий тип Option:

type Option<T> = {
  map: () => T
}

Затем я создаю новый интерфейс B от A и Option:

interface B {
  foo: Option<number>
  bar: Option<string>
}

Как я могу сделать эту операцию более общей? То есть. API, который я хочу, это:

type B = Lift<A>

Где Lift автоматически сопоставляет каждый элемент A с Option. Обратите внимание, что A может иметь любое количество членов любого типа.

Как я могу реализовать Lift? Если это невозможно в TypeScript, у кого-нибудь есть решение Scala/Haskell?

Ответы

Ответ 1

Хорошие новости: TypeScript 2.1.0, теперь это возможно через Mapped Types:

type Option<T> = { map() => T };
type OptionsHash<T> = { [K in keyof T]: Option<T[K]> };
function optionsFor<T>(structure: T): OptionsHash<T> { ... };

let input = { foo: 5, bar: 'X' };
let output = optionsFor(input);
// output is now typed as { foo: { map: () => number }, bar: { map: () => string } }

Возможно также и обратное:

function retreiveOptions<T>(hash: OptionsHash<T>): T { ... };

let optionsHash = {
    foo: { map() { return 5; } },
    bar: { map() { return 'x'; } }
};
let optionsObject = retreiveOptions(optionsHash);
// optionsObject is now typed as { foo: number, bar: string }

Ответ 2

Вы ищете более высокосортные типы. Здесь он находится в Scala:

trait FooBar[M[_]] {
  val foo: M[Integer]
  val bar: M[String]
}

type Identity[X] = X
type A = FooBar[Identity]
type B = FooBar[Option]

Вы можете использовать любые типы второго порядка, например:

type C = FooBar[List]

Но они не будут компилироваться:

// type S = FooBar[String] ---> String is a first-order type
// type M = FooBar[Map]    ---> Map[K, V] is a third-order type

К сожалению, это еще не дошло до TypeScript, но для него есть открытая проблема: https://github.com/Microsoft/TypeScript/issues/1213

Ответ 3

Я не смотрел на TypeScript какое-то время (я думаю, это было вокруг версии 1.0), поэтому я не могу сказать, есть ли он там сейчас.

Для чего требуется системная функция типа, называемая более высокими типами; он позволяет строить типы, передавая их в качестве аргументов для конструкторов типов, очень похожих на функциональное приложение, поднятых до уровня типа.

Вам нужно будет настроить определение A, чтобы сделать эту работу. Здесь, как я достиг бы чего вы хотите в Haskell:

-- First, I need a more general definition for A
data GeneralizedA f = A { foo :: f Int, bar :: f String }

-- So that I can re-encode the original A like this :
type A = GeneralizedA Identity

-- Guessing what the Option type would be since
-- Haskell type system is more precise here :
data Option a = Option { optionMap :: IO a }

-- And here the result :
type B = GeneralizedA Option