2011-10-10 2 views
7

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

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] 

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

Заранее благодарен!

+0

Я хотел бы добавить, что код, показанный здесь отрывок в результате рефакторинга одного из исходных классов, где подстановка черта должна наноситься. Исходный код полностью функциональный, включая упрощение выражений. Если я упрощаю процедуру no-op, также работает восстановленный код. –

+0

Есть ли вероятность, что вы сможете реорганизовать больше по этим строкам: http://www.artima.com/pins1ed/case-classes-and-pattern-matching.html? – huynhjl

+0

Вот откуда я пришел с моей начальной версией. Тем не менее, мне нужно сделать это признаком смешения, а не классом case, потому что я хочу применить его к нескольким классам, которые связаны только удаленно. Это должно быть возможно с помощью экстрактора, но я не могу его скомпилировать. –

ответ

0

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

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

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

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

< декламация >

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

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

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

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

</декламация >

Смежные вопросы