6

型クラスを使用してJson.toJsonfunctionを実装する Play フレームワークの JSON ライブラリを使用しています。(リフレクションなど、静的型付けの少ない別の手法を使用することにするかもしれませんが、Scala 型システムの学習に役立つため、今のところこのライブラリを使用したいと考えています。)

に渡す必要がある単純なケース クラスがたくさんあるtoJsonので、それぞれに暗黙的Writes[T]なオブジェクトを実装する必要があります。各クラスの最初のカットは次のようになります。

// An example class
case class Foo(title: String, lines: List[String])

// Make 'Foo' a member of the 'Writes' typeclass
implicit object FooWrites extends Writes[Foo] {
  def writes(f: Foo) : JsValue = {
    val fields = Seq("title" -> toJson(f.title), 
                     "lines" -> toJson(f.lines))                        
    JsObject(fields)
  }
}  

各クラスには同様の暗黙的な値があるため、以下のように共通部分を抽象化できます。しかし、型を宣言する方法がわからないため、これはコンパイルされません。

def makeSimpleWrites[C](fields: (String, C => T??)*) : Writes[C] = {
  new Writes[C] {
    def writes(c: C) : JsValue = {
      val jsFields = fields map { case (name, get) => (name, toJson(get(c)))}
      JsObject(jsFields)
    }
  }
}

implicit val fooWrites : Writes[Foo] = 
    makeSimpleWrites[Foo]("title" -> {_.title}, "lines" -> {_.lines})                                 
implicit val otherWrites ...

問題は、T渡したい型ですmakeSimpleWrites。T は の項目ごとに異なるため、通常の型パラメーターにすることはできませんfields。これは存在感のあるタイプですか?私はまだこれらのうちの1つを使用していません。シンタックスで大暴れ…

def makeSimpleWrites[C](fields: (String, C=>T forSome { type T; implicit Writes[T] })*) 

これはScalaで可能ですか?もしそうなら、構文は何ですか?

4

3 に答える 3

3

各フィールドには異なる型があるため、フィールドごとに 1 つの型パラメーターが必要になります。これは、これらのフィールドを書き込むにはWrites、対応する型のインスタンスを (暗黙的に) ( method にtoJson) 提供する必要があり、それらは静的に解決されるためです。

これを回避する 1 つの解決策は、プロセスを 2 つの部分に分割することです。各フィールドに対して呼び出す 1 つのメソッドを使用して、フィールド アクセサーを抽出し、対応するWriteSインスタンスでパックします (これは、ペアから暗黙的な変換を行うこともできます)。すでに渡されている)、および全体を取得して最終的なWriteSインスタンスを作成する 1 つのメソッド。このようなもの(実例、未テスト):

class WriteSFieldAccessor[C,T] private ( val title: String, val accessor: C => Any )( implicit val writes: Writes[T] )

implicit def toWriteSFieldAccessor[C,T:Writes]( titleAndAccessor: (String, C => T) ): WriteSFieldAccessor = {
  new WriteSFieldAccessor[C,T]( titleAndAccessor._1, titleAndAccessor._2 )
}
def makeSimpleWrites[C](fields: WriteSFieldAccessor[C,_]*) : Writes[C] = {
  new Writes[C] {
    def writes(c: C) : JsValue = {
      val jsFields = fields map { f: WriteSFieldAccessor => 
        val jsField = toJson[Any](f.accessor(c))(f.writes.asInstanceOf[Writes[Any]])
        (f.title, jsField)
      }
      JsObject(jsFields)
    }
  }
}

// Each pair below is implicitly converted to a WriteSFieldAccessor  instance, capturing the required information and passing it to makeSimpleWrites
implicit val fooWrites : Writes[Foo] = makeSimpleWrites[Foo]("title" -> {_.title}, "lines" -> {_.lines}) 

興味深い部分はtoJson[Any](f.accessor(c))(f.writes..asInstanceOf[Writes[Any]]). Any静的型として渡すだけです、(通常は暗黙的な)Writesインスタンスを明示的に渡します。

于 2012-12-13T19:00:00.073 に答える
1

"title" -> {s:Section => s.title}私の最初の解決策ではの代わりに書かなければならないという制限に対処しようとしたとき"title" -> {_.title}、私はそれを少しいじりましたが、常に scala の推論制限で実行するだけでした。そこで、別の角度からこの問題に取り組むことにし、まったく異なる解決策を思いつきました。これは基本的に準 DSL です。

class ExpandableWrites[C]( val fields: Vector[(String, C => Any, Writes[_])] ) extends Writes[C] {
  def and[T:Writes](fieldAccessor: C => T)(fieldName: String): ExpandableWrites[C] = {
    new ExpandableWrites( fields :+ (fieldName, fieldAccessor, implicitly[Writes[T]]) )
  }
  def writes(c: C) : JsValue = {
    val jsFields = fields map { case (name, get, writes) => (name, toJson[Any](get(c))(writes.asInstanceOf[Writes[Any]]) )}
    JsObject(jsFields)
  }
}

class UnaryExpandableWritesFactory[C] {
  def using[T:Writes](fieldAccessor: C => T)(fieldName: String): ExpandableWrites[C] = {
    new ExpandableWrites[C]( Vector( (fieldName, fieldAccessor, implicitly[Writes[T]] ) ) )
  }
}

def makeSimpleWritesFor[C] = new UnaryExpandableWritesFactory[C]

implicit val fooWrites : Writes[Foo] = 
  makeSimpleWritesFor[Foo].using(_.title)("title") .and (_.lines)("lines") .and (_.date)("date")

アイデアは、Writesインスタンスを段階的に作成し、新しいフィールドを 1 つずつ追加するというものです。.and唯一の煩わしさは、ドットを含む区切り記号が必要なことです。ドットがない (つまり、中置表記を使用している) と、コンパイラは再び混乱しているように見え(_.title)(s:Section => s.title).

于 2012-12-14T08:59:27.800 に答える