【问题标题】:Is it possible to have generic decorators in TypeScript that could be chained per their input/output types?是否可以在 TypeScript 中使用可以根据输入/输出类型链接的通用装饰器?
【发布时间】:2019-03-11 18:59:19
【问题描述】:

对于我们的一些集成,我们的代码库中有相当“模板化”的实现,可以方便地放入“管道和过滤器”模式恕我直言。

“组件”可以看起来像以下类型:

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 世界中提供的类似“可组合性”)

【问题讨论】:

    标签: javascript typescript decorator finagle


    【解决方案1】:

    您对泛型的使用让我有些困惑,因为您似乎没有明确类型参数 variables 和您插入其中的具体类型之间的区别。更不用说您使用非 TS 术语,如 valNone。无论如何,以下是编译的东西,可能给你你正在寻找的那种行为:

    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
    

    我认为这与您想要的相似?希望有帮助;祝你好运!


    编辑:做同样事情但不用担心conditional typespolymorphic 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'> { }
    

    其余的应该和以前一样。再次祝你好运!

    【讨论】:

    • 这很有趣。尤其是带有polymorphic thisconditional types 的那个。 EDIT 案例也很有趣。绝对可以玩这个。 None 被暗示为“自定义类型”而不是 TS 的一部分。也将val 修复为const - 忘记了。但感谢您提供这些建议并花时间提供帮助。这 might 只是工作 :) 但是为什么参数中的字符串文字而不是实际类型呢? @jcalz
    • 字符串文字:除了方便之外没有其他原因。如果你有真正的类型,你应该能够使用它们来代替它们。
    • 第二种实现似乎需要run 方法,不是吗?
    【解决方案2】:

    这是我得到的:

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

    我已经简化了代码:&lt;Xin, Xout, Yin, Yout&gt;&lt;T,U&gt;,但这很容易适应。

    按预期输入。在运行时,Component&lt;...,X&gt;.andThen(Component&lt;Y,...&gt;) 被检测为无效(首先是TS2345)。

    轻微的重构,调用.run的不是链本身(即Component) - 半小时后,我找不到在编译时而不是运行时检测@987654328的方法@ 由Component&lt;..., null&gt;(即链的最后一个组件)调用。

    相反,我将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'))
    

    我希望这就是你要找的。​​p>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-02
      • 2011-10-29
      • 2016-10-09
      • 1970-01-01
      • 2022-06-15
      • 2020-05-16
      • 2014-03-18
      • 2021-12-24
      相关资源
      最近更新 更多