web-dev-qa-db-ja.com

暗黙の「コンストラクターパラメーター」を取るとトレイトを宣言する方法は?

私はクラス階層を設計しています。これは、基本クラスといくつかの特性で構成されています。基本クラスはいくつかのメソッドのデフォルト実装を提供し、トレイトはabstract overrideを介して特定のメソッドを選択的にオーバーライドして、スタック可能なトレイト/ミックスインとして機能します。

設計の観点からは、これはうまく機能し、ドメインにマップされるため、ここ(別の特性)からの述語を使用して、ここ(1つの特性)からフィルター機能を追加できます。

ただし、ここで、私の特性のいくつかに暗黙のパラメーターを指定する必要があります。これはデザインの観点からはまだ意味があり、実際には混乱しないことを嬉しく思います。しかし、私はコンパイラーをそれで実行するように説得することはできません。

問題の核心は、トレイトにコンストラクター引数を提供できないため、暗黙的にマークされる可能性があることです。メソッド実装内で暗黙的なパラメーターを参照すると、予期される「暗黙的な値が見つかりませんでした」というメッセージが表示されてコンパイルに失敗します。私は暗黙のうちに、構築段階(実際には、常にスコープ内にあります)から、メソッド内で利用可能になるまで暗黙的に「伝播」しようとしました

implicit val e = implicitly[ClassName]

しかし、(間違いなく多くの人が期待しているように)that定義が同じメッセージで失敗しました。

ここでの問題は、コンパイラにtrait自体のシグネチャにimplicit ClassNameフラグをタグ付けし、呼び出し側(つまり、traitをオブジェクトに混ぜる人)に暗黙の提供を強制することができないことです。 。現在、私の呼び出し元はそうしていますがそうしていますが、コンパイラはこのレベルでチェックしていません。


構築時に特定の暗黙的変数が使用可能であることを要求するものとして特性をマークする方法はありますか?

(そうでない場合、これは単にまだ実装されていないのですか、これが非実用的であるより深い理由がありますか?)

38
Andrzej Doyle

実際、私はこれをかなり頻繁に望んでいましたが、このアイデアを思いついたばかりです。翻訳できます

trait T(implicit impl: ClassName) {
  def foo = ... // using impl here
}

[編集済み:元のバージョンでは、他のメソッドの暗黙的アクセスが提供されていませんでした]

trait T {
  // no need to ever use it outside T
  protected case class ClassNameW(implicit val wrapped: ClassName)

  // normally defined by caller as val implWrap = ClassNameW 
  protected val implWrap: ClassNameW 

  // will have to repeat this when you extend T and need access to the implicit
  import implWrap.wrapped

  def foo = ... // using wrapped here
}
16
Alexey Romanov

これは不可能です。

しかし、implicitlyとScalaの型推論を使用して、これを可能な限り簡単にすることができます。

trait MyTrait {

    protected[this] implicit def e: ClassName

}

その後

class MyClass extends MyTrait {

    protected[this] val e = implicitly // or def

}

簡潔で、拡張クラスに型を記述する必要さえありません。

14
Paul Draper

私はこの問題に数回遭遇しました、そして確かにそれは少し厄介ですが、それほど多くありません。抽象メンバーとパラメーターは通常、同じことを行うための2つの代替方法であり、その長所と短所があります。抽象メンバーを持つトレイトは、とにかくトレイトを実装するために別のクラスが必要になるため、それほど不便ではありません。*

したがって、実装するクラスが暗黙的に提供する必要があるように、トレイトに抽象値宣言を含める必要があります。次の例をご覧ください。正しくコンパイルされ、指定された特性を実装する2つの方法が示されています。

trait Base[T] {
    val numT: Ordering[T]
}
/* Here we use a context bound, thus cannot specify the name of the implicit
 * and must define the field explicitly.
 */
class Der1[T: Ordering] extends Base[T] {
    val numT = implicitly[Ordering[T]]
    //Type inference cannot figure out the type parameter of implicitly in the previous line
}
/* Here we specify an implicit parameter, but add val, so that it automatically
 * implements the abstract value of the superclass.
 */
class Der2[T](implicit val numT: Ordering[T]) extends Base[T]

私が示す基本的なアイデアはKnut Arne Vedaaの回答にもありますが、不要な機能の使用をやめ、より説得力のある便利な例を作成しようとしました。

*これが、トレイトがパラメータを受け入れられない理由ではありません-わかりません。この場合、制限は許容できると主張しているだけです。

11
Blaisorblade

これは不可能のように見えるので、基本クラスのコンストラクターで暗黙のvalを宣言するオプションを選びました。質問で指摘されているように、これは理想的ではありませんが、コンパイラーを満たし、実用的には、私の特定のケースではそれほど負担にはなりません。

誰かがより良い解決策を持っているなら、私はそれを聞いて受け入れたいです。

0
Andrzej Doyle

あなたはこのようにすることができます:

abstract class C

trait A { this: C =>
    val i: Int
}    

implicit val n = 3

val a = new C with A {
    val i = implicitly[Int]
}

しかし、そこにポイントがあるかどうかはわかりません。暗黙の値を明示的に参照することもできます。

あなたが望むのはインスタンス化でiの実装を取り除くことだと思いますが、あなたが言うように、問題の核心はトレイトがコンストラクターパラメーターを取らないことです-それらが暗黙的であろうと暗黙的であろうと関係ありません。

この問題の考えられる解決策は、すでに有効な構文に新しい機能を追加することです。

trait A {
    implicit val i: Int
}

ここで、iは、暗黙がスコープ内にある場合、コンパイラーによって実装されます。

0
Knut Arne Vedaa