Scala 编译器无法推断模式匹配的混合类型

标签 scala types mixins inference

我有一个关于有限置换集上的代数群的用例。因为我想将该组用于其他不相关的各种排列类,所以我想将此作为混合特征。这是我尝试的摘录

trait Permutation[P <: Permutation[P]] { this: P =>
  def +(that: P): P

  //final override def equals(that: Any) = ...
  //final override lazy val hashCode = ...

  // Lots of other stuff
}

object Permutation {
  trait Sum[P <: Permutation[P]] extends Permutation[P] { this: P =>
    val perm1, perm2: P

    // Lots of other stuff
  }

  private object Sum {
    def unapply[P <: Permutation[P]](s: Sum[P]): Some[(P, P)] = Some(s.perm1, s.perm2)
    //def unapply(s: Sum[_ <: Permutation[_]]): Some[(Permutation[_], Permutation[_])] = Some(s.perm1, s.perm2)
  }

  private def simplify[P <: Permutation[P]](p: P): P = {
    p match {
      case Sum(a, Sum(b, c)) => simplify(simplify(a + b) + c)

      // Lots of other rules

      case _ => p
    }
  }
}

在某个时间点,我想调用simple 方法,以便使用代数公理来简化群运算的表达式。使用模式匹配似乎很有意义,因为有很多公理需要评估,而且语法很简洁。但是,如果我编译代码,我会得到:
error: inferred type arguments [P] do not conform to method unapply's type parameter bounds [P <: Permutation[P]]

我不明白为什么编译器不能正确推断类型,我不知道如何帮助它。实际上,在这种情况下进行模式匹配时,P 的参数类型是无关紧要的。如果 p 是任何排列总和,则模式应该匹配。返回类型仍然是 P,因为转换完全是通过调用 P 上的 + 运算符来完成的。

因此,在第二次尝试中,我换入了注释掉的 unapply 版本。但是,然后我从编译器(2.8.2)收到断言错误:
assertion failed: Sum((a @ _), (b @ _)) ==> Permutation.Sum.unapply(<unapply-selector>) <unapply> ((a @ _), (b @ _)), pt = Permutation[?>: Nothing <: Any]

有什么线索可以让编译器接受这个吗?

提前致谢!

最佳答案

经过两天的培育,我终于找到了一个可以在没有警告的情况下编译并通过我的规范测试的解决方案。以下是我的代码的可编译摘录,以显示所需内容。但是请注意,该代码是空操作,因为我省略了实际执行排列的部分:

/**
 * A generic mix-in for permutations.
 * <p>
 * The <code>+</code> operator (and the apply function) is defined as the
 * concatenation of this permutation and another permutation.
 * This operator is called the group operator because it forms an algebraic
 * group on the set of all moves.
 * Note that this group is not abelian, that is the group operator is not
 * commutative.
 * <p>
 * The <code>*</code> operator is the concatenation of a move with itself for
 * <code>n</code> times, where <code>n</code> is an integer.
 * This operator is called the scalar operator because the following subset(!)
 * of the axioms for an algebraic module apply to it:
 * <ul>
 * <li>the operation is associative,
 *     that is (a*x)*y = a*(x*y)
 *     for any move a and any integers x and y.
 * <li>the operation is a group homomorphism from integers to moves,
 *     that is a*(x+y) = a*x + a*y
 *     for any move a and any integers x and y.
 * <li>the operation has one as its neutral element,
 *     that is a*1 = m for any move a.
 * </ul>
 * 
 * @param <P> The target type which represents the permutation resulting from
 *        mixing in this trait.
 * @see Move3Spec for details of the specification.
 */
trait Permutation[P <: Permutation[P]] { this: P =>
  def identity: P

  def *(that: Int): P
  def +(that: P): P
  def unary_- : P

  final def -(that: P) = this + -that
  final def unary_+ = this

  def simplify = this

  /** Succeeds iff `that` is another permutation with an equivalent sequence. */
  /*final*/ override def equals(that: Any): Boolean // = code omitted
  /** Is consistent with equals. */
  /*final*/ override def hashCode: Int // = code omitted

  // Lots of other stuff: The term string, the permutation sequence, the order etc.
}

object Permutation {
  trait Identity[P <: Permutation[P]] extends Permutation[P] { this: P =>
    final override def identity = this

    // Lots of other stuff.
  }

  trait Product[P <: Permutation[P]] extends Permutation[P] { this: P =>
    val perm: P
    val scalar: Int

    final override lazy val simplify = simplifyTop(perm.simplify * scalar)

    // Lots of other stuff.
  }

  trait Sum[P <: Permutation[P]] extends Permutation[P] { this: P =>
    val perm1, perm2: P

    final override lazy val simplify = simplifyTop(perm1.simplify + perm2.simplify)

    // Lots of other stuff.
  }

  trait Inverse[P <: Permutation[P]] extends Permutation[P] { this: P =>
    val perm: P

    final override lazy val simplify = simplifyTop(-perm.simplify)

    // Lots of other stuff.
  }

  private def simplifyTop[P <: Permutation[P]](p: P): P = {
    // This is the prelude required to make the extraction work.
    type Pr = Product[_ <: P]
    type Su = Sum[_ <: P]
    type In = Inverse[_ <: P]
    object Pr { def unapply(p: Pr) = Some(p.perm, p.scalar) }
    object Su { def unapply(s: Su) = Some(s.perm1, s.perm2) }
    object In { def unapply(i: In) = Some(i.perm) }
    import Permutation.{simplifyTop => s}

    // Finally, here comes the pattern matching and the transformation of the
    // composed permutation term.
    // See how expressive and concise the code is - this is where Scala really
    // shines!
    p match {
      case Pr(Pr(a, x), y) => s(a*(x*y))
      case Su(Pr(a, x), Pr(b, y)) if a == b => s(a*(x + y))
      case Su(a, Su(b, c)) => s(s(a + b) + c)
      case In(Pr(a, x)) => s(s(-a)*x)
      case In(a) if a == a.identity => a.identity
      // Lots of other rules

      case _ => p
    }
  } ensuring (_ == p)

  // Lots of other stuff
}

/** Here's a simple application of the mix-in. */
class Foo extends Permutation[Foo] {
  import Foo._

  def identity: Foo = Identity
  def *(that: Int): Foo = new Product(this, that)
  def +(that: Foo): Foo = new Sum(this, that)
  lazy val unary_- : Foo = new Inverse(this)

  // Lots of other stuff
}

object Foo {
  private object Identity
  extends Foo with Permutation.Identity[Foo]

  private class Product(val perm: Foo, val scalar: Int)
  extends Foo with Permutation.Product[Foo]

  private class Sum(val perm1: Foo, val perm2: Foo)
  extends Foo with Permutation.Sum[Foo]

  private class Inverse(val perm: Foo)
  extends Foo with Permutation.Inverse[Foo]

  // Lots of other stuff
}

如您所见,解决方案是定义对于simpleTop 方法而言是本地的类型和提取器对象。

我还包含了一个关于如何将这种混合应用到 Foo 类的小例子。如您所见,Foo 只不过是一个用于组合其自身类型的排列的工厂。如果您有许多这样的类,而这些类在其他方面并不相关,那么这将是一个很大的好处。

<咆哮>

但是,我忍不住要说 Scala 的类型系统非常复杂!我是一名经验丰富的 Java 库开发人员,对 Java 泛型非常精通。然而,我花了两天时间才弄明白了包含三个类型和对象定义的六行代码!如果这不是出于教育目的,我会放弃这种方法。

现在,我很想预言 Scala 不会因为这种复杂性而成为编程语言方面的下一个大事件。如果您是一名 Java 开发人员,现在对 Java 泛型感到有点不舒服(不是我),那么您会讨厌 Scala 的类型系统,因为至少可以说,它为 Java 泛型的概念添加了不变量、协变和逆变。

总而言之,Scala 的类型系统似乎面向更多的科学家而不是开发人员。从科学的角度来看,推断程序的类型安全性很好。从开发人员的角度来看,弄清这些细节的时间是浪费的,因为它使它们远离程序的功能方面。

没关系,我肯定会继续使用 Scala。模式匹配、混入和高阶函数的组合非常强大,不容错过。但是,我觉得 Scala 将是一种效率更高的语言,而无需它过于复杂的类型系统。

关于Scala 编译器无法推断模式匹配的混合类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7709182/

相关文章:

Scala:定义带参数和可调用的函数

ember.js - 在 Ember.js 中创建新的 mixin 时,如何扩展多个 mixin

css - Sass mixins 和占位符不能一起工作

php - MySQL,如何为 SET 列选择所有可能的值

php - 特化中的参数类型协方差

c# - 分离 C# 构造函数的前基和后基或链式构造函数调用指令

scala - Play 应用程序中的全局异常处理

scala - 如何根据类名创建 Akka Actor

scala - Play Framework ,Scala : authenticate User by Role

arrays - 将 reversed() 赋给 swift 数组有什么麻烦?