代码之家  ›  专栏  ›  技术社区  ›  Daniel Shin

如何在不使用依赖类型的情况下进行模式匹配?

  •  0
  • Daniel Shin  · 技术社区  · 8 年前

    这很难表达,所以请让我举个例子:

    trait Cache
    
    trait QueryLike {
      type Result
    }
    
    trait Query[A] extends QueryLike {
      type Result = A
      def exec: Result
    }
    
    trait CachedQuery[A] extends QueryLike {
      type Result = A
      def execWithCache(cache: Cache): Result
    }
    
    def exec(query: QueryLike)(implicit cache: Cache): query.Result = query match {
      case q: Query[query.Result] => q.exec
      case cq: CachedQuery[query.Result] => cq.execWithCache(cache)
    }
    

    当模式匹配在不同类型上完成时,它可以很好地编译和运行( Query , CachedQuery )而不是依赖泛型 this question .

    但我仍然收到编译器警告,如:

    警告:(18,12)抽象类型在类型模式A$A4中的结果。this.Query[Query.Result]未选中,因为它已被删除 案例q:Query[Query.Result]=>q、 执行

    因为我不处理依赖类型 query.Result 不管怎么说(就像为不同的操作铸造它一样),最好是将其完全擦除并取消警告。但不幸的是,使用通配符并没有很好的理由:

    ...
    case q: Query[_] => q.exec // type mismatch
    case cq: CachedQuery[_] => cq.execWithCache(cache)
    ...
    

    有没有更好的方法在不生成编译器警告的情况下做到这一点?

    2 回复  |  直到 8 年前
        1
  •  2
  •   Michael Zajac    8 年前

    此错误并非特定于依赖路径的类型。如果你试图匹配任何 Query[A] 您将得到相同的错误,因为type参数 A 在运行时被擦除。在这种情况下,类型参数不可能是您要查找的类型以外的任何类型。自 查询[A] 是一个 QueryLike { type Result = A} ,它也应该是 Query[query.Result] ,尽管这是一种不寻常的看待方式。你 能够 使用 @unchecked 注释,如果您愿意,可以取消警告:

    def exec(query: QueryLike)(implicit cache: Cache): query.Result = query match {
      case q: Query[query.Result @unchecked] => q.exec
      case cq: CachedQuery[query.Result @unchecked] => cq.execWithCache(cache)
    }
    

    虽然很难说这是否适用于您的实际用例,但您也可以重构代码以避免完全匹配,并通过多态性(可能)更优雅地处理它。自从上次 exec 需要隐式 Cache 无论如何 ,让每个人都这么做似乎没有什么坏处 QueryLike 。通过这种方式,您的API可以更加统一,并且您不需要确定要调用哪个方法。

    trait Cache
    
    trait QueryLike {
      type Result
      def exec(implicit cache: Cache): Result
    }
    
    trait Query[A] extends QueryLike {
      type Result = A
    }
    
    trait CachedQuery[A] extends QueryLike {
      type Result = A
    }
    
    def exec(query: QueryLike)(implicit cache: Cache): query.Result = query.exec
    

    如果 查询[A] 需要一个 执行 没有 隐藏物 ,您还可以使用 DummyImplicit 让它在没有人的情况下工作。

    def exec(implicit d: DummyImplicit): Result
    
        2
  •  0
  •   dth    8 年前

    实际上,这个问题似乎非常特定于依赖路径的类型:问题是q和cq的类型。q、 结果是与查询不兼容的类型。结果,因为Scala类型检查器不知道,所以该查询和q和qc必须是相同的引用。

    因此,Query[Query.Result]实际上需要运行时类型检查。当我试图删除类型参数并只使用内部结果时,我注意到了这一点。然后模式匹配不再生成警告,但q.exec的返回类型将与query.Result不兼容。

    不使用@unchecked或asInstanceOf等的一个解决方案是将Result转换为QueryLike的类型参数。一般来说,对于希望“脱离上下文”自由传递的内容,不应该使用抽象类型成员。因此,将类型成员转换为继承层次结构中某个位置的类型参数有点奇怪。

    因此,正如编译器所知,这很好,因为他不必检查类型参数:

    trait QueryLike[A] {
    
    }
    
    trait Query[A] extends QueryLike[A] {
      def exec: A
    }
    
    trait CachedQuery[A] extends QueryLike[A] {
      def execWithCache(cache: Cache): A
    }
    
    def exec[A](query: QueryLike[A])(implicit cache: Cache): A = query match {
      case q: Query[A] => q.exec
      case cq: CachedQuery[A] => cq.execWithCache(cache)
    }
    

    另一种方法是将该方法添加到CachedQuery和Query的公共基本特征中。

    trait QueryLike {
      type Result
    }
    
    trait Query[A] extends QueryLike with ExecWithCache {
      type Result = A
      def exec: Result
      override def execWithCache(implicit cache: Cache) = exec
    }
    
    trait CachedQuery[A] extends QueryLike with ExecWithCache {
      type Result = A
      def exec(cache: Cache): Result
      override def execWithCache(implicit cache: Cache) = exec(cache)
    }
    
    trait ExecWithCache extends QueryLike {
      def execWithCache(implicit cache: Cache): Result
    }
    

    要改变这一点,Scala可能必须能够确定两个稳定的访问器何时相同。