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

发布于 2024-12-08 20:47:04 字数 1545 浏览 0 评论 0原文

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

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
    }
  }
}

在某个时间点,我想调用简化方法,以便使用代数公理简化群运算的表达式。使用模式匹配似乎很有意义,因为有很多公理需要评估并且语法很简洁。但是,如果我编译代码,我会得到:

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]

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

提前致谢!

I have a use case for algebraic groups over finite permutation sets. Because I would like to use the group for various permutation classes which are otherwise unrelated, I would like to do this as a mix-in trait. Here's an excerpt of my attempt

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
    }
  }
}

At some point in time, I would like to call the simplify method in order to, well, simplify an expression of group operations using the algebraic axioms. Using pattern matching seems to make sense as there are a lot of axioms to be evaluated and the syntax is concise. However, if I compile the code, I get:

error: inferred type arguments [P] do not conform to method unapply's type parameter bounds [P <: Permutation[P]]

I do not understand why the compiler cannot infer the type correctly and I don't know how to help it. Actually, the parameter type of P is irrelevant when pattern matching in this case. If p is any Sum of permutations, the pattern should match. The return type is still a P because the transformation is solely done by calling the + operator on P.

So in a second attempt I swap in the commented out version of unapply. However, then I get an assertion error from the compiler (2.8.2):

assertion failed: Sum((a @ _), (b @ _)) ==> Permutation.Sum.unapply(<unapply-selector>) <unapply> ((a @ _), (b @ _)), pt = Permutation[?>: Nothing <: Any]

Any clues how I can make the compiler accept this?

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

哭泣的笑容 2024-12-15 20:47:04

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

/**
 * 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
}

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

我还提供了一个小示例,说明如何将此类混合应用到 Foo 类。正如您所看到的,Foo 只不过是一个用于其自身类型的组合排列的工厂。如果您有许多这样的原本不相关的类,那么这是一个很大的好处。

<咆哮>

然而,我不得不说 Scala 的类型系统极其复杂!我是一位经验丰富的 Java 库开发人员,并且对 Java 泛型非常精通。然而我花了两天时间才弄清楚包含三种类型和对象定义的六行代码!如果这不是出于教育目的,我就会放弃这种方法。

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

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

没关系,我肯定会继续使用 Scala。模式匹配、混合和高阶函数的组合太强大了,不容错过。然而,我认为如果没有过于复杂的类型系统,Scala 将是一种更加高效的语言。

After breeding over this for two days, I have finally found a solution which compiles without warning and passes my specification test. Following is a compilable excerpt of my code to show what is required. Note however, that the code is a no-op because I have left out the parts to actually perform the permutations:

/**
 * 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
}

As you can see, the solution is to define types and extractor objects which are local to the simplifyTop method.

I have also included a little example of how to apply such a mix-in to the class Foo. As you can see, Foo is little more than a factory for composed permutations of its own type. That's a big benefit if you have many classes like this which are otherwise unrelated.

<rant>

However, I cannot resist to say that Scala's type system is insanely complex! I'm a seasoned Java library developer and feel very proficient with Java Generics. Yet it took me two days to figure out the six lines of code with the three type and object definitions! If this were not for educational purposes, I would have ditched the approach.

Right now, I am tempted to oracle that Scala will NOT be the next big thing in terms of programming languages because of this complexity. If you are a Java developer who feels a little uncomfortable about Java generics now (not me), then you would hate Scala's type system because it adds invariants, covariants and contravariants to the concept of Java generics, to say the least.

All-in-all, Scala's type system seems to address more scientists than developers. From a scientific point of view, it's nice to reason about the type safety of a program. From a developers point of view, the time to figure out these details is wasted because it keeps them away from the functional aspects of the program.

Nevermind, I will continue with Scala for sure. The combination of pattern matching, mix-ins and high-order functions is just too powerful to miss. However, I feel Scala would be a much more productive language without it's overly complex type system.

</rant>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文