Компилятор Scala не может определить смешанный тип для сопоставления с образцом - PullRequest
7 голосов
/ 10 октября 2011

У меня есть пример использования алгебраических групп над конечными множествами подстановок. Поскольку я хотел бы использовать группу для различных классов перестановок, которые в противном случае не связаны, я хотел бы сделать это как черту смешивания. Вот выдержка из моей попытки

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]

Любые подсказки, как я могу заставить компилятор принять это?

Заранее спасибо!

1 Ответ

0 голосов
/ 11 октября 2011

После двухдневного изучения я наконец-то нашел решение, которое компилируется без предупреждения и проходит мой тест спецификации.Ниже приведен скомпилированный фрагмент моего кода, чтобы показать, что требуется.Тем не менее, обратите внимание, что код не работает, потому что я упустил части для фактического выполнения перестановок:

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

Как видите, решение состоит в том, чтобы определить типы и объекты экстрактора, которые являются локальнымик методу simpifyTop.

Я также включил небольшой пример того, как применить такой смешанный класс к классу Foo.Как вы можете видеть, Foo - чуть больше, чем фабрика для составных перестановок своего собственного типа.Это большое преимущество, если у вас есть много подобных классов, которые иначе не связаны.

Однако я не могу удержаться от того, чтобы сказать, что система типов Scala безумно сложна!Я опытный разработчик библиотеки Java и очень хорошо разбираюсь в Java Generics.Однако мне потребовалось два дня, чтобы выяснить шесть строк кода с определениями трех типов и объектов!Если бы это было не в образовательных целях, я бы отказался от этого подхода.

Прямо сейчас, я испытываю соблазн оракула, что Scala НЕ будет следующей большой вещью с точки зрения языков программирования из-за этой сложности.Если вы Java-разработчик, который сейчас чувствует себя немного неловко из-за обобщений Java (не из-за меня), то вам не понравится система типов Scala, потому что она добавляет, по меньшей мере, инварианты, коварианты и противоречия к концепции обобщений Java.

В целом, система типов Scala, похоже, адресована большему количеству ученых, чем разработчиков.С научной точки зрения приятно рассуждать о безопасности типов программ.С точки зрения разработчиков, время, чтобы выяснить эти детали, потрачено впустую, потому что оно не позволяет им работать с функциональными аспектами программы.Сочетание сопоставления с образцом, смешивания и функций высокого порядка слишком мощное, чтобы его упустить.Однако я считаю, что Scala был бы гораздо более продуктивным языком без его слишком сложной системы типов.

...