4

単純な休息リクエストに Scala タイプセーフ ビルダー パターンを使用しています。これは流暢な API としてうまく機能します。

sealed abstract class Method(name: String)

case object GET extends Method("GET")
case object POST extends Method("POST")

abstract class TRUE
abstract class FALSE

case class Builder[HasMethod, HasUri](
  method: Option[Method],
  uri: Option[String]) {

  def withMethod(method: Method): Builder[TRUE, HasUri] = copy(method = Some(method))
  def withUri(uri: String): Builder[HasMethod, TRUE] = copy(uri = Some(uri))
}

implicit val init: Builder[FALSE, FALSE] = Builder[FALSE, FALSE](None, None)

//Fluent examples
val b1: Builder[TRUE, FALSE] = init.withMethod(GET)
val b2: Builder[TRUE, TRUE] = init.withMethod(GET).withUri("bar")

Methodインスタンスをインスタンスに変換できるようにすることで、これをより DSL に似せたいと思いますがBuilder、ビルダーを暗黙的にインクルードしようとするとinit、暗黙的な変換と型パラメーターの組み合わせがコンパイラーを混乱させます。

implicit def toMethod[HasUri](m: Method)
  (implicit builder: Builder[_, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m)

// ** ERROR **: could not find implicit value for parameter builder: 
//              Builder[_, HasUri]  
val b3: Builder[TRUE, TRUE] = GET withUri "foo"

// However the implicit parameter is discovered fine when function is called directly
val b4: Builder[TRUE, FALSE] = toMethod(GET)
val b5: Builder[TRUE, TRUE] = toMethod(GET) withUri "foo"

b3 を除くすべての行がコンパイルされます。関数が明示的に呼び出されると、toMethodbuilder パラメーターが暗黙的に検出されます。また、ジェネリック引数 (およびタイプ セーフ) を削除すると、コードは期待どおりに機能します。

これは、scala の暗黙的な変換の制限ですか? または、これを達成するための正しい構文がありませんか?

ユーザーが独自の初期ビルダーにビルダーのフィールドの一部のデフォルト値を提供できるように、初期ビルダー インスタンスを暗黙的に検出したいと考えています。

更新しました

修正しようとしているのは暗黙の変換だけなので、例を単純にするために一部のコードを省略しています。

タイプ セーフなビルダー パターンの概要は、http: //blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.htmlで詳しく説明されています。

その後は、メソッドと uri がある場合にのみbuildメソッドを呼び出すことができます。Builder

暗黙のパラメーターとしてビルダーを発見したい理由は、DSL で次のケースをサポートするためです。

url("http://api.service.org/person") apply { implicit b =>
  GET assert(Ok and ValidJson)
  GET / "john.doe" assert(NotFound)
  POST body johnDoeData assert(Ok)
  GET / "john.doe" assert(Ok and bodyIs(johnDoeData))
}

これらの場合

  1. 指定された uri で新しいビルダーが作成されます。url
  2. これは、クロージャの横で次のように再利用されます。implicit b =>
  3. asserturi とメソッドが指定されているため、メソッドのみが使用可能です
  4. 現在の/uri に追加されます。これは、ビルダーに uri が指定されているためにのみ使用できます。

method と uri を指定する別の例

GET url("http://api.service.org/secure/person") apply { implicit b =>
  auth basic("harry", "password") assert(Ok and ValidJson)
  auth basic("sally", "password") assert(PermissionDenied)
}
4

2 に答える 2

1

あなたの暗黙的な解決の問題は、Scala 型システムの制限に起因するものではないと感じていますが、ここで指定する存在型に依存します。

implicit def toMethod[HasUri](m: Method)
  (implicit builder: Builder[_, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m)

私が間違っていなければ、この場合、存在型は Nothing として扱われます。すべての可能な Scala クラスのサブクラスは存在しないため、メソッドは実際には次のようになります。

implicit def toMethod[HasUri](m: Method)
  (implicit builder: Builder[Nothing, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m)

次に、Scala は現在のスコープを調べて、メソッドに提供する Builder[Nothing,HasUri] のサブクラスを見つけますが、Builder[Nothing,HasUri] 以外に必要な型に一致するクラスはありません。これは、ビルダー クラスが不変であるためです。つまり、Builder[A,B]<:<Builder[C,D]IFA=:=C & B=:=D

したがって、次の 2 つのオプションがあります。

  • パラメータを署名に追加すると、toMethod[HasUri] は toMethod[A,HasUri] になります
  • 型分散の Scala の正しい実装を活用する

Builder[A,HasUri] が Builder[Nothing,HasUri] のサブクラスであることを強制したいので、

Nothing <:< A for any A

Builder がその最初の型パラメーターで反変的であるBuilder[A,HasUri] <:< Builder[B,HasUri]場合に強制したい場合。B<:<A型の前に - 記号を付けることで、矛盾を強制します。

Builder[-HasMethod, HasUri]HasMethod では反変であり、HasUri では不変です


結論

型システムは強力ですが、単純なタスクであっても複雑なパターンを使用することは必須ではありません:

  • HasUri はメソッド toMethod の型パラメーターであるため、m からは推測されません。
  • _ を使用して消去するため、 HasMethod は推論されません。

引数が解決に関与していない場合、2 つのジェネリック引数を持つ暗黙的なパラメーターを持つことのポイントは何ですか? 私は単に書くだろう:

case class DefaultBuilder(m:Method) extends Builder[True,HasUri]

誰かがすでに言ったように、このような状況に陥った場合、それはあなたの設計が問題に対して間違っているからです。ビルダーが toMethod で暗黙的でなければならない理由を説明できますか?

implicit def toMethod(m:Method) = DefaultBuilder(m)
于 2013-05-17T15:13:28.747 に答える
1

このコードは現在、Scala 2.11 でそのまま動作しますが、Scala 2.10 (この元のコードを記述するために使用していた) では動作しません。

これが当てはまる理由を探していたところ、scala-lang の jira でこのバグを見つけることしかできませんでした。

https://issues.scala-lang.org/browse/SI-3346

Scala 2.10 でこれを解決するためにいくつかの方法を試しましたが、できませんでした。これらには、@ Edmondo1984 の提案が含まれており、以下のようにHasMethodおよびHasUriパラメータを制限しています。

case object GET extends Method("GET")
case object POST extends Method("POST")

sealed trait TBool
trait TTrue extends TBool
trait TFalse extends TBool

case class Builder[HasMethod <: TBool, HasUri <: TBool](method: Option[Method],
                                                        uri: Option[String]) {

  def withMethod(method: Method): Builder[TTrue, HasUri] = copy(method = Some(method))
  def withUri(uri: String): Builder[HasMethod, TTrue] = copy(uri = Some(uri))
}

object Builder {
  implicit val init: Builder[TFalse, TFalse] = Builder[TFalse, TFalse](None, None)

  // Example build method
  implicit class CanExecute(builder: Builder[TTrue, TTrue]) {
    def execute(): String = s"Build(${builder.method} ${builder.uri}"
  }
}


//Fluent examples
val b1: Builder[TTrue, TFalse] = init.withMethod(GET)
val b2: Builder[TTrue, TTrue] = init.withMethod(GET).withUri("bar")


implicit def toMethod[HasUri <: TBool](m: Method)
                                      (implicit builder: Builder[_, HasUri]): Builder[TTrue, HasUri] = builder.withMethod(m)

// ** ERROR **: could not find implicit value for parameter builder:
//              Builder[_, HasUri]
// ** BUT ** Works in Scala 2.11
val b3: Builder[TTrue, TTrue] = GET withUri "foo"

GET withUri "foo" execute ()
于 2016-03-14T21:51:01.053 に答える