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 >
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. –
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
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ć. –