Возможно ли иметь универсальные декораторы в TypeScript, которые могли бы быть связаны в цепочке по их типам ввода / вывода? - PullRequest
0 голосов
/ 11 марта 2019

Для некоторых наших интеграций у нас есть довольно «шаблонизированные» реализации в нашей кодовой базе, которые удобно помещать в шаблон «труба и фильтр» ИМХО.

«Компоненты» можно сделать так, чтобы они выглядели какследующий тип (ы):

class Component1<In, Out, Xin, Xout>
class Component2<Xin, Xout, Yin, Yout>
class Component3<Yin, Yout> // only has 2 params but could be <Yin, Yout, None, None> for a custom 'None' type

Идея состоит в том, чтобы иметь что-то, что позволяло бы их "связать в цепочку", чтобы что-то вроде этого:

const c1 = new Component1<A,B,C,D>(...) //perhaps pass the param types in constructor? Other options?
const c2 = new Component2<C,D,E,F>(...)
const c3 = new Component3<E,F, None, None>(...)

const chain = c1.andThen(c2).andThen(c3) // The "last" item in the chain would "always" be a component of type <X,Y, None, None>

chain.run() // Not sure if this is needed but to make it clear that something "runs" this chain

Я не могуподумать о каком-либо «универсальном» способе создания этих компонентов, где эта цепочка может быть «определена» во время компиляции, чтобы ограничить, какие компоненты могут быть связаны с другими (т. е. типы ввода / вывода должны совпадать).Таким образом, за c1 может следовать только c2, но не c3 - но после c3.

ничего нельзя связать. Возможно ли это?Что-нибудь, чтобы приблизить это достаточно?

(Для любопытных: пытаться достичь такой же «компоновки», которую Finagle предлагает в мире Scala)

Ответы [ 2 ]

1 голос
/ 12 марта 2019

Вот что у меня есть:

class Component<T, U> {
    constructor(private t: T, private u: U) {}
    andThen<V>(component: Component<U, V>): Component<U, V> {
        // implement andThen
        return component;
    }
    static run<T>(component: Component<T, null>) {
        // implement run
    }
}

type A = 'a'; const a: A = 'a';
type B = 'b'; const b: B = 'b';
type C = 'c'; const c: C = 'c';

const c1 = new Component<A, B>(a, b);
const c2 = new Component<B, C>(b, c);
const c3 = new Component<C, null>(c, null);

c2.andThen(c1); // TS2345: A is not assignable to B
Component.run(c1.andThen(c2)); // TS2345: Component<B,C> not assignable to Component<B,null>
Component.run(c1.andThen(c2).andThen(c3));

Я упростил код: <Xin, Xout, Yin, Yout><T,U>, но его легко адаптировать.

Цепочка напечатана, как и ожидалось. Во время выполнения Component<...,X>.andThen(Component<Y,...>) определяется как недействительный (первый TS2345).

Небольшой рефакторинг, не сама цепочка (то есть Component) вызывает .run - через полчаса я не смог найти способ обнаружить, во время компиляции, а не во время выполнения, что .run был вызван Component<..., null> (т.е. последний компонент цепочки).

Вместо этого я ставлю run как статический метод Component, и он принимает в качестве входных данных только последний компонент . Использование продемонстрировано в последних двух строках

И последнее, но не менее важное: класс был очень общим и полиморфным, поэтому многие компоненты могут быть объединены в цепочку!

(new Component<'a', 'b'>('a', 'b'))
.andThen(new Component<'b', 'c'>('b', 'c'))
.andThen(new Component<'c', 'd'>('c', 'd'))
.andThen(new Component<'d', 'e'>('d', 'e'))
.andThen(new Component<'e', 'f'>('e', 'f'))
.andThen(new Component<'f', 'g'>('f', 'g'))

Надеюсь, это то, что вы искали.

1 голос
/ 11 марта 2019

Использование вами обобщений немного сбивает меня с толку, так как не похоже, что вы прояснили разницу между параметром типа переменные и конкретными типами, которые вы подключаете к ним.Не говоря уже о том, что вы используете не-TS термины, такие как val и None.В любом случае, следующее - это то, что компилируется, и может дать вам то поведение, которое вы ищете:

type NotNever<T, Y=T, N=never> = [T] extends [never] ? N : Y;

// just create types, don't worry about implementation
declare class BaseComponent<In, Out, Xin=never, Xout=never> {
  // make BaseComponent depend structurally on type parameters
  i: In;
  o: Out;
  xi: Xin;
  xo: Xout;

  // andThen() is generic, and only accepts the right kind of other component
  // only callable if Xin and Xout are *not* never
  andThen<Yin, Yout>(
    this: NotNever<Xin | Xout, this>,
    c: BaseComponent<Xin, Xout, Yin, Yout>
  ): BaseComponent<In, Out, Yin, Yout>;

  // run() is only callable if Xin and Xout *are* never
  run(this: BaseComponent<In, Out, never, never>): void;
}

// create some concrete subclasses where parameters are set with string literal types
class Component1 extends BaseComponent<'In', 'Out', 'Xin', 'Xout'> { }
class Component2 extends BaseComponent<'Xin', 'Xout', 'Yin', 'Yout'> { }
class Component3 extends BaseComponent<'Yin', 'Yout'> { }

Вы можете увидеть, как это работает:

const c1 = new Component1();
const c2 = new Component2();
const c3 = new Component3();

c1.andThen(c1); // error
c1.andThen(c2); // okay
c1.andThen(c3); // error
c1.run(); // error

c2.andThen(c1); // error
c2.andThen(c2); // error
c2.andThen(c3); // okay
c2.run(); // error

c3.andThen(c1); // error
c3.andThen(c2); // error
c3.andThen(c3); // error
c3.run(); // okay

const chain = c1.andThen(c2).andThen(c3) // BaseComponent<'In', 'Out', never, never>;
chain.run(); // okay

Я думаю, это похоже на то, что вы хотите?Надеюсь, это поможет;удачи!


РЕДАКТИРОВАТЬ: Еще один способ сделать то же самое, но не беспокоясь о условные типы и полиморфные this заключается в следующем:

// one base class for the end of the chain
declare class EndComponent<In, Out> {
  i: In;
  o: Out;
  run(): void;
}

// another base class for intermediate parts of the chain
declare class PipeComponent<In, Out, Xin, Xout> {
  i: In;
  o: Out;
  xi: Xin;
  xo: Xout;
  // andThen() is overloaded 
  andThen<Yin, Yout>(
    c: PipeComponent<Xin, Xout, Yin, Yout>
  ): PipeComponent<In, Out, Yin, Yout>;
  andThen(c: EndComponent<Xin, Xout>): EndComponent<In, Out>;
}

class Component1 extends PipeComponent<'In', 'Out', 'Xin', 'Xout'> { }
class Component2 extends PipeComponent<'Xin', 'Xout', 'Yin', 'Yout'> { }
class Component3 extends EndComponent<'Yin', 'Yout'> { }

Остальные должны вести себя как прежде.Еще раз удачи!

...