2011-10-10 10 views
7

Mam przypadek użycia dla grup algebraicznych nad skończonymi zestawami permutacji. Ponieważ chciałbym użyć tej grupy dla różnych klas permutacji, które w przeciwnym razie nie są powiązane, chciałbym to zrobić jako cechę mieszanki. Oto fragment mojego próbieKompilator Scala nie może wywnioskować typu mieszania do dopasowywania wzorców.

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

w pewnym momencie w czasie, chciałbym wywołać metodę uprościć, aby dobrze, uproszczenia wyraz operacji grupowych z wykorzystaniem algebraicznych aksjomaty. Używanie dopasowywania wzorców wydaje się mieć sens, ponieważ istnieje wiele aksjomatów do oceny, a składnia jest zwięzła. Jednakże, jeśli mogę skompilować kod, otrzymuję:

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

Nie rozumiem dlaczego kompilator nie może ustalić typ poprawnie, a ja nie wiem jak mu pomóc. W rzeczywistości typ parametru P jest nieistotny przy dopasowywaniu wzorca w tym przypadku. Jeśli p jest dowolną Sumą permutacji, wzorzec powinien być zgodny. Zwracany typ to nadal P, ponieważ transformacja odbywa się wyłącznie przez wywołanie operatora + na P.

Więc w drugiej próbie zamieniam skomentowaną wersję anulowania. Jednak wtedy pojawia się błąd twierdzenia z kompilatora (2.8.2):

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

Wszelkie wskazówki, jak mogę zrobić kompilator to zaakceptować?

Z góry dzięki!

+0

Dodam, że kod pokazany tutaj jest fragmentem wynikające z refaktoringu jednego z oryginalnych klas, gdzie cecha permutacji powinny być stosowane do. Oryginalny kod jest w pełni funkcjonalny, łącznie z uproszczeniem wyrażeń. Jeśli uprościę uproszczenie, kod refaktoryzacji też działa. –

+0

Jest szansa, że ​​będziesz mógł więcej dokonać refaktoryzacji w następujący sposób: http://www.artima.com/pins1ed/case-classes-and-pattern-matching.html? – huynhjl

+0

To tam pochodzę z moją pierwszą wersją. Muszę jednak wprowadzić tę cechę mieszanki, a nie klasę przypadków, ponieważ chcę zastosować ją w kilku klasach, które są tylko zdalnie połączone. To powinno być możliwe za pomocą ekstraktora, ale nie mogę tego skompilować. –

Odpowiedz

0

Po dwóch dniach hodowli nad tym, znalazłem rozwiązanie, które kompiluje bez ostrzeżenia i przechodzi test specyfikacji. Poniżej znajduje się kompilowany fragment mojego kodu, aby pokazać, co jest wymagane. Należy jednak pamiętać, że kod jest nie-op bo pominięta części faktycznie wykonywać permutacje:

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

Jak widać, rozwiązanie jest określenie rodzajów i przedmiotów ekstrakcyjnych, które są lokalne dla uprość metodęTop.

Podam także mały przykład zastosowania takiego miksu do klasy Foo. Jak widać, Foo to niewiele więcej niż fabryka dla złożonych permutacji własnego typu. To duża korzyść, jeśli masz wiele takich klas, które nie są ze sobą powiązane.

<rant>

Jednak nie mogę się oprzeć, aby powiedzieć, że układ typu Scala jest szalenie skomplikowane! Jestem doświadczonym programistą Java i czuję się bardzo biegły w Java Generics. Jednak zajęło mi dwa dni, aby znaleźć sześć linii kodu z trzema definicjami typów i obiektów! Gdyby nie było to dla celów edukacyjnych, porzuciłbym podejście.

W tej chwili mam pokusę, aby wyrocznia, że ​​Scala NIE będzie kolejną wielką rzeczą pod względem języków programowania z powodu tej złożoności. Jeśli jesteś programistą Java, który czuje się trochę niekomfortowo z powodu generycznych Java (teraz nie ja), to nienawidziłbyś systemu typu Scala, ponieważ dodaje on niezmienniki, kowariancje i przeciwwskazania do pojęcia genericów Java, co najmniej.

Ogólnie rzecz biorąc, system typu Scala wydaje się adresować więcej naukowców niż programistów. Z naukowego punktu widzenia dobrze jest mówić o bezpieczeństwie typu programu. Z punktu widzenia programistów czas na poznanie tych szczegółów jest marnowany, ponieważ uniemożliwia im funkcjonalne aspekty programu.

Nieważne, będę kontynuować z Scala na pewno. Połączenie dopasowywania wzorców, mieszania i funkcji wyższego rzędu jest po prostu zbyt potężne, aby je pominąć. Uważam jednak, że Scala byłaby dużo bardziej produktywnym językiem bez zbyt skomplikowanego systemu.

</rant >

Powiązane problemy