【问题标题】:How can I verify type existence on compile time in Scala如何在 Scala 的编译时验证类型是否存在
【发布时间】:2020-08-26 15:26:19
【问题描述】:

我有以下特质和职业:

sealed trait Signal

sealed trait Description[T]

final case class S1(name: String) extends Signal

final case class D1(name: String) extends Description[S1]

我试图实现的是,任何想要添加 Signal 的人都可以(在编译时)创建描述。

我不想更改Description 的签名,但肯定不会更改Signal 的签名

我将编译器设置为在警告时失败,因此我可以利用我的 ADT 已密封这一事实。

我的想法是有这样一个“编译守卫”:

def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }

但我收到以下错误:

<console>:17: error: type mismatch;
 found   : D1
 required: Description[S]
       def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }
                                                                                              ^

【问题讨论】:

    标签: scala generics types pattern-matching shapeless


    【解决方案1】:
    def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }
    

    由于与

    相同的原因无法编译
    def returnItself[S <: Signal](s: S): S = s match { case S1(name) => S1(name) }
    

    这里详细解释原因:

    Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?

    Type mismatch on abstract type used in pattern matching

    如果您不想将 Description 逻辑与 ADT 混合或手动定义类型类的实例,例如 SignalMapper,您可以使用 Shapeless

    import shapeless.ops.coproduct.Mapper
    import shapeless.{:+:, CNil, Coproduct, Generic, Poly1}
    
    def compilationGuard[C <: Coproduct]()(implicit
      gen: Generic.Aux[Signal, C],
      mapper: Mapper[uniqueDescriptionPoly.type, C]
    ) = null
    
    object uniqueDescriptionPoly extends Poly1 {
      implicit def cse[S <: Signal, C1 <: Coproduct](implicit
        gen1: Generic.Aux[Description[S], C1],
        ev: C1 <:< (_ :+: CNil)
      ): Case.Aux[S, Null] = null
    }
    
    compilationGuard()
    

    测试:

    final case class S1(name: String) extends Signal
    final case class S2(name: String) extends Signal
    final case class D1(name: String) extends Description[S1] 
    // doesn't compile
    
    final case class S1(name: String) extends Signal
    final case class S2(name: String) extends Signal
    final case class D1(name: String) extends Description[S1]
    final case class D2(name: String) extends Description[S1]
    // doesn't compile
    
    final case class S1(name: String) extends Signal
    final case class S2(name: String) extends Signal
    final case class D1(name: String) extends Description[S1]
    final case class D2(name: String) extends Description[S2]
    // compiles
    

    【讨论】:

    • 这似乎无法编译:``` 找不到参数 gen 的隐含值:shapeless.Generic.Aux[Signal,C] compilationGuard() ```
    • @NoamShaish 下面的代码编译scastie.scala-lang.org/YDGlShJdTq6j76zIJO43bQ
    • 你能帮我理解为什么这不能编译吗:scastie.scala-lang.org/RwBjDWGnQJij3gcZLP0AuQ
    • @NoamShaish Trait Description 必须密封(以及Signal)。
    • @NoamShaish @implicitNotFound can annotate 类或隐式参数,而不是方法。所以要么做def compilationGuard[C &lt;: Coproduct]()( implicit gen: Generic.Aux[Signal, C], @implicitNotFound("No unique Description for all children of Signal") mapper: Mapper[uniqueDescriptionPoly.type, C] ) = null,要么创建一个类型类并注释它。
    【解决方案2】:

    您的程序失败了,因为它无法证明类型 SS1

    你可以引入类型类来代替模式匹配,它会在编译时进行映射:

    trait SignalMapper[S] { //typeclass handling of mapping S to D
      type D <: Description[S]
    
      def map(signal: S): D
    }
    
    //instance of typeclass SignalMapper for S1
    //if you'd put it in a companion object of S1, it would be always in scope
    object S1 { 
    
      implicit val mapperS1: SignalMapper[S1] = new SignalMapper[S1] {
        type D = D1
    
        def map(signal: S1) = D1(signal.name)
      }
    
    }
    

    然后你可以重写compileGuard为:

    def compilationGuard[S <: Signal](s: S)(implicit mapper: SignalMapper[S]): Description[S] = mapper.map(s)
    

    Scastie

    【讨论】:

    • 这种方法的问题是它只会在使用新类型的compileGuard时编译失败。
    • 你能举个例子吗?
    • 如果没有调用所有类型的compilationGuard,这不会失败。如果没有像:scala compilationGuard(S2("hello")) 这样的调用,它不会失败
    【解决方案3】:

    我试图实现的是,任何想要添加 Signal 的人都将拥有(在编译时创建描述。

    这样做的简单方法是使其成为Signal 的一部分:

    sealed trait Signal[S <: Signal[S, D], D <: Description[S]] {
      // optionally
      def description: D
    }
    
    final case class S1(name: String) extends Signal[S1, D1] {
      def description = D1(name)
    }
    

    sealed trait Signal[S <: Signal[S]] {
      type Descr <: Description[S]
      // optionally
      def description: Descr
    }
    
    final case class S1(name: String) extends Signal[S1] {
      type Descr = D1
      def description = D1(name)
    }
    

    当然也不是很简单

    sealed trait Signal[S <: Signal] {
      def description: Description[S]
    }
    

    取决于您的要求。

    【讨论】:

    • 那是在我的 ADT 中混合逻辑,我想避免这种情况,所以 Signal 是纯 ADT,可以使用 pureconfig 等工具从文件中加载
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 2017-06-10
    • 2021-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多