3

型パラメーターを型メンバーに移動したいと思います。

これは機能する出発点です:

trait Sys[S <: Sys[S]] {
  type Tx
  type Id <: Identifier[S#Tx]
}

trait Identifier[Tx] {
  def dispose()(implicit tx: Tx): Unit
}

trait Test[S <: Sys[S]] {
  def id: S#Id
  def dispose()(implicit tx: S#Tx) {
    id.dispose()
  }
}

私を悩ませているのは、[S <: Sys[S]]ライブラリ全体で型パラメーターを持ち歩いていることです。だから私が考えていたのはこれです:

trait Sys {
  type S = this.type  // ?
  type Tx
  type Id <: Identifier[S#Tx]
}

trait Identifier[Tx] {
  def dispose()(implicit tx: Tx): Unit
}

trait Test[S <: Sys] {
  def id: S#Id
  def dispose()(implicit tx: S#Tx) {
    id.dispose()
  }
}

これは失敗します...S#Txそして、S#Idどういうわけか切り離されました:

error: could not find implicit value for parameter tx: _9.Tx
               id.dispose()
                         ^

それを機能させるトリックや変更はありますか?


編集:明確にするために、私は主にタイプを修正して機能させることを望んでSSysます。私の場合、パス依存型を使用すると多くの問題があります。pedrofuria と Owen の回答を反映した例を 1 つだけ挙げると、次のようになります。

trait Foo[S <: Sys] {
  val s: S
  def id: s.Id
  def dispose()(implicit tx: s.Tx) {
    id.dispose()
  }
}

trait Bar[S <: Sys] {
  val s: S
  def id: s.Id
  def foo: Foo[S]
  def dispose()(implicit tx: s.Tx) {
    foo.dispose()
    id.dispose()
  }
}

<console>:27: error: could not find implicit value for parameter tx: _106.s.Tx
               foo.dispose()
                          ^

def foo: Foo[s.type]これがどこにも通じないという考えをあなたに与えるためにそれを作ってみてください.

4

4 に答える 4

2

Testコンパイルされるバージョンは次のとおりです。

trait Test[S <: Sys] {
  val s : S
  def id: s.Id
  def dispose()(implicit tx: s.Tx) {
    id.dispose()
  }
}

「S#TxとS#Idがなんとなく離れた」というおっしゃる通りです。私が理解しているように、両方の S で実際に同じタイプであるとは限りません。

于 2013-01-08T23:57:30.063 に答える
2

これは、ペドロフルラの回答に対するコメントほどの回答ではありません。これは正しいと思います。理由を説明しましょう。

Scala には面白い点があります。クラスの型メンバーを記述すると、基本的に 2 つの異なる名前が作成されます。1 つはクラスに属し、もう 1 つはそのクラスのオブジェクトに属します。それらの間には何らかの関係があります。つまり、オブジェクト メンバー型はクラス メンバー型のサブタイプである必要がありますが、私の経験では、この接続を使用することはほとんどありません。ほとんどの場合、これらは完全に別個のものと考えるべきです。

ここで本当にやりたかったのは、2 つのタイプをパッケージ化して、それらのペアに名前を付けることができるようにすることです。だから私は次のように書きますSys

trait Sys {
    type Tx
    type Id <: Identifier[Tx]
}

それは、魔法や綿毛なしで、あなたがやりたいことを正確に言っているからです。それぞれが2つのものを格納するタイプのオブジェクトを作成し、それらはタイプです(そしてそれらの間にいくつかの制約があります)。

Test次に、ペドロフルラが提案する方法を書くことができます:

trait Test {
    val s: Sys
    def id: s.Id
    def dispose()(implicit tx: s.Tx) {
        id.dispose()(tx)
    }
}

繰り返しますが、必要なものだけを追加する必要はありませTestSysSysTest

言い換えれば、パッケージ化されて渡される通常の古い値として型を考えてみてください。


編集

スケーラビリティ(少なくともあなたの例では、私が考えていないものがあるかもしれません)は、必要なものに正確に固執する場合、問題にはなりません. あなたのFoo/Bar例では、

// This is normal; nothing unexpected.
trait Foo {
    val s: Sys
    def id: s.Id
    def dispose()(implicit tx: s.Tx) {
        id.dispose()
    }
}

trait Bar { self =>
    val s: Sys
    def id: s.Id
    // Now here's the key!
    val foo: Foo { val s: Sys { type Tx = self.s.Tx } }
    def dispose()(implicit tx: s.Tx) {
        foo.dispose()
        id.dispose()
    }
}

ここで、私たちが本当に望んでいるfooのは、それが私たちs.Txの と同じであることです。したがって、まさにそれを要求するだけで、問題なくコンパイルされます。 s.Tx

于 2013-01-09T00:10:41.483 に答える
1

これはあなたの質問に答えませんが(既存のコードの最小限の変更を保証します)、ここに考えがあります:

TxタイプがのメンバーでありSys、で使用される代わりにIdentifier、開始点として、それをのパラメーターにし、次Sysのように、との両方Id <: Identifierで同じように使用されていることを確認しS <: Sysます。

    trait Sys[Tx] {
        type S <: Sys[Tx]
        type Id <: Identifier[Tx]
    }

    trait Identifier[Tx] {
        def dispose()(implicit tx: Tx): Unit
    }

    trait Test[Tx, S <: Sys[Tx]] {
        def id: S#Id
        def dispose()(implicit tx: Tx) = id.dispose()
    }

これはあなたのモチベーションに関してはほとんど改善されません(Sysまだタイプパラメータがあります)が、私の次のステップはTxタイプメンバーに変換することです。val s: Sただし、トリック(およびそれに基づくタイプ)を使用せずに機能させる唯一の方法は、次のとおりです。

  • 2つの特性に分割し、タイプおよびその他すべてのホルダーとして(および内部特性として)Sys導入し、それがあなたのために行っている他のすべての特性を保持しますOuterSysTxSysIdentifierSys
  • Test特性を持っているOuterSys

コードは次のとおりです。

    trait OuterSys {
        type Tx
        type S <: Sys
        type Id <: Identifier

        trait Sys {
        }

        trait Identifier {
            def dispose()(implicit tx: Tx): Unit
        }

        trait Test {
            def id: Id
            def dispose()(implicit tx: Tx) = id.dispose()
        }
    }

ですから、あなたの質問に実際に答えたり、問題を解決したりすることはありませんが、少なくともこれをどのように解決するかについて、皆さんに何らかのアイデアを与えることができると期待していました。私が試した他のすべては、コンパイラがインスタンスのいくつかのインスタンスを叫び、Sそれに基づくタイプを期待して戻ってきました。


編集:分割する必要はありませんSys

    trait Sys {
        type Tx
        type Id <: Identifier

        trait Identifier {
            def dispose()(implicit tx: Tx): Unit
        }

        trait Test {
            def id: Id
            def dispose()(implicit tx: Tx) = id.dispose()
        }
    }

また、明らかなことを言及することを怠りました-タイプはインスタンスに依存しSysますが、これは理にかなっていると思います(システム間で識別子を共有しないのですか?トランザクションかもしれませんか?)。

Sysインスタンス内からも「テスト」する必要はなく、type S <: Sysこれ以上(およびtype S = this.typeMySystemで)必要はありません。

    object MySystem extends Sys {
        type Tx = MyTransaction
        type Id = MyIdentifier

        class MyTransaction (...)
        class MyIdentifier (...) extends Identifier {
            def dispose()(implicit tx: MySystem.Tx) {}
        }
    }

    object MyOuterTest {
    {
        def id: MySystem.Id = new MySystem.MyIdentifier(...)

        def dispose()(implicit tx: MySystem.Tx) {
            id.dispose()
        }
    }
于 2013-01-09T01:55:37.800 に答える
1

コンパイルできるバージョンが 2 つありますが、どちらがライブラリで探しているものか完全にはわかりません。(編集: このバージョンには本質的に欠陥があります。コメントを参照してください)。ここでは、型パラメーター S を Sys から完全に削除し、引き続き型射影を使用します (対パス依存型)。

trait Sys {
  type Tx
  type Id <: Identifier[Sys#Tx]
}

trait Identifier[Tx] {
  def dispose()(implicit tx: Tx)
}

trait Test[S <: Sys] {
  def id: S#Id
  def dispose()(implicit tx: S#Tx) {
    id.dispose()(tx)
  }
}

このバージョンでは、型パラメーターを型メンバーに変換し (これが正しい変換かどうかは完全にはわかりません)、型の改良と型射影の組み合わせを使用して、Test で正しい型を保証します。

trait Sys {
  type S <: Sys
  type Tx
  type Id <: Identifier[S#Tx]
}

trait Identifier[Tx] {
  def dispose()(implicit tx: Tx)
}

trait Test[A <: Sys {type S = A}] {
  def id: A#Id
  def dispose()(implicit tx: A#S#Tx) {
    id.dispose()
  }
}

A#S#Txまた、暗黙的なパラメーターの型投影として使用する必要があることにも注意してください。これにより、理由が明らかS#IdS#Txなり、「分離」されることが期待されます。実際には、それらは切り離されておらず、宣言type S = this.typeするSとシングルトン型が作成されS#T、パス依存型が作成されます。

より明確にするために、 givenval a: A {type B}は のa.A省略形ですa.type#A。つまり、上記の回答でコンパイルにが必要だったように、 は型射影であり、必要なパス依存型ではS#Tないため、this.type#T単純に宣言def dispose()(implicit tx: S#S#T)するだけではうまくいかない理由でもあります。S#S#Tval s: S

編集:次のように、テストのパラメーターを削除できます。

trait Test {
  type A <: Sys {type S = A}
  def id: A#Id
  def dispose()(implicit tx: A#S#Tx) {
    id.dispose()
  }
}

ただし、これには多くのソース コードの変更が必要になる場合があります。

型パラメーターを使用するか、型メンバーを使用するかに関係なく、型を指定しても、ライブラリで型がどのように機能するかを修正しない限り、単に消えるわけではありません。S <: Sys[S]つまり、型パラメーターと抽象型メンバーは同等であるため、型を完全に取り除くことはできないようです。

EDIT2 : パス依存型または Duduk の回答に沿ったものを使用しないと、これは不可能のようです。パスに依存する型を公開するために、型メンバーと a に変更する必要がval s: Sあるため、ライブラリで使用できない場合があります。Identifier[Tx]def id: S#Idval

trait Sys {self =>
  type Tx
  type Id <: Identifier {type Tx = self.Tx}
}

trait Identifier {
  type Tx
  def dispose()(implicit tx: Tx)
}

trait Test[S <: Sys] {
  val id: S#Id
  def dispose()(implicit tx: id.Tx) {
    id.dispose()(tx)
  }
}
于 2013-01-09T09:33:45.397 に答える