2011-10-10 5 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です。

2回目の試行では、コメントアウトされたunapplyのバージョンと入れ替えます。しかし、その後、私は、コンパイラ(2.8.2)からのアサーションエラーを取得:

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

任意の手掛かり私は、コンパイラはこれを受け入れることができますか?

ありがとうございます!

+0

私はここに示すコードは順列形質を適用しなければならない元のクラスの一つのリファクタリング起因抜粋であることを追加する必要があります。元のコードは、表現の簡略化を含め、完全に機能しています。簡略化をノーオペレーションにすると、リファクタリングされたコードも機能します。 –

+0

これらの行に沿ってさらにリファクタリングすることができます:http://www.artima.com/pins1ed/case-classes-and-pattern-matching.html? – huynhjl

+0

これは、私が最初のバージョンから来ているところです。しかし、私は、このクラスをケースクラスではなく、ミックスインの特性にする必要があります。遠隔接続されているいくつかのクラスに適用したいからです。これはエクストラクタでは可能ですが、これをコンパイルすることはできません。 –

答えて

0

これを2日間飼育した後、私は最終的に警告なしにコンパイルし、仕様テストに合格するソリューションを見つけました。以下は、必要なことを示すためのコードの抜粋です。

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

あなたが見ることができるように、解決策はに対してローカルな種類や抽出オブジェクトを定義することです:私は実際に置換を実行するために部品を省いているので、コードは何もしませんので、しかし、注意してください簡略化トップメソッド。

このようなミックスインをFooクラスに適用する方法の少しの例も示しました。ご覧のように、Fooは独自のタイプの組み換えパーミュテーションの工場です。これは、そうでなければ無関係なこのような多くのクラスを持っているなら、大きな利点です。

<暴言>

しかし、私は、Scalaの型システムがめちゃくちゃ複雑であることを言って抵抗することはできません!私は経験豊富なJavaライブラリの開発者であり、Java Genericsに非常に堪能です。しかし、3つの型定義とオブジェクト定義で6行のコードを理解するのに2日かかってしまいました!これが教育上の目的ではない場合、私はそのアプローチを捨てたでしょう。

今、私はこの複雑さのためにScalaがプログラミング言語の面で次の大きなものにならないことをオラクルに誘惑しています。もしあなたがJavaのジェネリックス(私ではない)に少し不快だったJava開発者なら、Scalaの型システムはJavaのジェネリックスの概念に不変項、共変変数、反変分を加えるので、あなたは嫌です。

Scalaのタイプのシステムは、開発者よりも多くの科学者に対処しているようです。科学的な観点から見ると、プログラムの型安全性については理にかなっています。開発者の視点から見ると、これらの詳細を把握する時間は、プログラムの機能面から遠ざけるため、無駄です。

私はScalaを使用していません。パターンマッチング、ミックスイン、および高次関数の組み合わせは、あまりにも欠かすほど強力です。しかし、私はScalaがそれほど複雑な型システムでなくてはるかに生産的な言語になると感じています。

< /暴言>

関連する問題