I have a use case for algebraic groups over finite permutation sets. Because I would like to use the group for various permutation classes which are otherwise unrelated, I would like to do this as a mix-in trait. Here’s an excerpt of my attempt
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
}
}
}
At some point in time, I would like to call the simplify method in order to, well, simplify an expression of group operations using the algebraic axioms. Using pattern matching seems to make sense as there are a lot of axioms to be evaluated and the syntax is concise. However, if I compile the code, I get:
error: inferred type arguments [P] do not conform to method unapply's type parameter bounds [P <: Permutation[P]]
I do not understand why the compiler cannot infer the type correctly and I don’t know how to help it. Actually, the parameter type of P is irrelevant when pattern matching in this case. If p is any Sum of permutations, the pattern should match. The return type is still a P because the transformation is solely done by calling the + operator on P.
So in a second attempt I swap in the commented out version of unapply. However, then I get an assertion error from the compiler (2.8.2):
assertion failed: Sum((a @ _), (b @ _)) ==> Permutation.Sum.unapply(<unapply-selector>) <unapply> ((a @ _), (b @ _)), pt = Permutation[?>: Nothing <: Any]
Any clues how I can make the compiler accept this?
Thanks in advance!
After breeding over this for two days, I have finally found a solution which compiles without warning and passes my specification test. Following is a compilable excerpt of my code to show what is required. Note however, that the code is a no-op because I have left out the parts to actually perform the permutations:
As you can see, the solution is to define types and extractor objects which are local to the simplifyTop method.
I have also included a little example of how to apply such a mix-in to the class Foo. As you can see, Foo is little more than a factory for composed permutations of its own type. That’s a big benefit if you have many classes like this which are otherwise unrelated.
<rant>
However, I cannot resist to say that Scala’s type system is insanely complex! I’m a seasoned Java library developer and feel very proficient with Java Generics. Yet it took me two days to figure out the six lines of code with the three type and object definitions! If this were not for educational purposes, I would have ditched the approach.
Right now, I am tempted to oracle that Scala will NOT be the next big thing in terms of programming languages because of this complexity. If you are a Java developer who feels a little uncomfortable about Java generics now (not me), then you would hate Scala’s type system because it adds invariants, covariants and contravariants to the concept of Java generics, to say the least.
All-in-all, Scala’s type system seems to address more scientists than developers. From a scientific point of view, it’s nice to reason about the type safety of a program. From a developers point of view, the time to figure out these details is wasted because it keeps them away from the functional aspects of the program.
Nevermind, I will continue with Scala for sure. The combination of pattern matching, mix-ins and high-order functions is just too powerful to miss. However, I feel Scala would be a much more productive language without it’s overly complex type system.
</rant>