Dopo averlo riprodotto per due giorni, ho finalmente trovato una soluzione che compila senza alcun preavviso e supera il mio test delle specifiche. Di seguito è un estratto compilabile del mio codice per mostrare ciò che è richiesto. Si noti, tuttavia, che il codice è un no-op perché ho lasciato fuori le parti di svolgere effettivamente le permutazioni:
/**
* 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
}
Come si può vedere, la soluzione è quella di definire i tipi e gli oggetti di aspirazione che sono locali al Semplifica il metodo Top.
Ho anche incluso un piccolo esempio di come applicare un tale mix-in alla classe Foo. Come puoi vedere, Foo è poco più di una fabbrica per permutazioni composte di un suo tipo. Questo è un grande vantaggio se hai molte classi come questa che sono altrimenti non correlate.
<sproloquio>
Tuttavia, non posso resistere a dire che il sistema di tipi di Scala è follemente complessa! Sono un esperto sviluppatore di librerie Java e mi sento molto abile con Java Generics. Eppure mi ci sono voluti due giorni per capire le sei righe di codice con le tre definizioni di tipo e oggetto! Se questo non fosse per scopi educativi, avrei abbandonato l'approccio.
In questo momento, sono tentato di oracle che Scala NON sarà la prossima grande novità in termini di linguaggi di programmazione a causa di questa complessità. Se sei uno sviluppatore Java che si sente un po 'a disagio nei confronti dei generici Java ora (non io), allora odiesti il sistema di tipi di Scala perché aggiunge invarianti, covarianti e controversi al concetto di generici Java, per usare un eufemismo.
Nel complesso, il sistema di tipo Scala sembra rivolgersi a più scienziati che agli sviluppatori. Da un punto di vista scientifico, è bello ragionare sulla sicurezza del tipo di un programma. Dal punto di vista degli sviluppatori, il tempo per capire questi dettagli è sprecato perché li tiene lontani dagli aspetti funzionali del programma.
Non importa, continuerò sicuramente con Scala. La combinazione di combinazioni di pattern, mix-in e funzioni di alto livello è troppo potente per mancare. Tuttavia, ritengo che Scala sarebbe un linguaggio molto più produttivo senza il suo sistema di tipi eccessivamente complesso.
</rant >
Dovrei aggiungere che il codice mostrato qui è un estratto risultante da un refactoring di una delle classi originali in cui il tratto permutazione Applicazione a. Il codice originale è completamente funzionale, inclusa la semplificazione delle espressioni. Se faccio la semplificazione un no-op, funziona anche il codice refactored. –
Ogni possibilità di refactoring di più in questo senso: http://www.artima.com/pins1ed/case-classes-and-pattern-matching.html? – huynhjl
Ecco da dove vengo con la mia versione iniziale. Tuttavia, ho bisogno di renderlo un tratto mix-in, non una classe case, perché voglio applicarlo a diverse classi che sono solo collegate in remoto. Questo dovrebbe essere possibile con un estrattore, ma non riesco a farlo compilare. –