1

変換関数を実行したくない場合は、いくつかのオプションがあります。高次関数を使用した変換オンオプションのスカラ型クラス

現在のオプションを処理するメソッドは次のようになります。

def writeOptionalXml[T](content: Option[T], mapFn: T => Xml): Xml = 
    content match { 
    case Some(c) => mapFn(c) 
    case None => NodeSeq.Empty 
} 

そして、それは素晴らしい作品。しかし、私はオプションではありませんが、空の文字列、空のxmlノードやいくつかのケースクラスなど、空のままであるその他の入力もあります。

私は型クラスにリファクタリングするのは良い学習体験と思っていました。多くのコードの論争の後、私はオプションのタイプを処理するために文脈の境界を使う必要があることを発見し、私は自分のタイプクラスの夢を達成するうえでうまくやっていると思った。

ここでは、変換関数(mapFnなどの名前が悪い)を使用しています。 (コンテンツ:Option [T]、mapFn:T => Xml): の場合は、 (入力:A、mapFn:A => Xml)のメソッドシグネチャが必要です。 :Xml

私はタイプシグネチャを変更することに苦労しました。[_]を使用して試してみました。

私は何でもある瞬間、で持っていますが、かなり、このようなコードの合成されたバージョン:

import scala.annotation.implicitNotFound 

object writableTypes extends App { 

    type Xml = String 
    @implicitNotFound("No member of type class in scope for ${T}") 
    trait WritableLike[A] { 
    def toXml[B](input: A, mapFn: ((_$1) forSome {type _$1}) => Xml): Xml 
    } 

    object WritableLike { 

    implicit object WritableLikeString extends WritableLike[String] { 
     override def toXml[B](input: String, mapFn: ((_$1) forSome {type _$1}) => Xml): Xml = 
     mapFn(input) 
    } 

    implicit def OptionFormat[T: WritableLike]: Object = new WritableLike[Option[T]] { 
     override def toXml[B](input: Option[T], mapFn: ((_$1) forSome {type _$1}) => Xml): Xml = 
     mapFn(input.get) 
    } 

    def writeXml[X](input: X, mapFn: ((_$1) forSome {type _$1}) => Xml)(implicit ev: WritableLike[X]): Xml = 
     ev.toXml[X](input, mapFn) 
    } 

    println(WritableLike.writeXml(Option(SomeCaseClass(5)), transformToXml)) 

    case class SomeCaseClass(content: Int) { def someMethod = ""} 

    def transformToXml[T](input: SomeCaseClass): String = input.someMethod 

} 

残念ながら、これは、このメソッドの呼び出し WritableLike.writeXml(オプション(であるためコンパイルされません。 SomeCaseClass(5))、transformToXml)

関数transformToXmlは、必要なメソッドシグネチャを満たしていません。

私はこれほど多くの順列を試してきましたが、それ以外の方法で解決策を見つけることはできません。

すべてをオプションにすることで簡単に解決できる方法があると確信していますが、私はそれを真に汎用的にするソリューションを見つけることにもっと興味があります。

私はこれを非常にうまく説明しているとは思えません。タイプクラスを書くのは私の最初の試みですが、私はそれは簡単だと思いましたが、解決しようとしている特定の問題がいくつか複雑です。

Scalaタイプのシステムで一般的なプログラミングをより深く理解してくださった方のお話をいただき、大変感謝しています。

おかげ

答えて

0

は、私はあなたが、プレーン機能で使用されるあなたのタイプのクラスでmapFnで少しハングアップしまったと思います。入力データのみに依存するように型クラスを構造化することができます。残りの作業は、型クラスのインスタンスを使用して行われます。ここでは入力データだけを頼るWritableLike型クラスの定義の例です:

trait WritableLike[A] { 
    def toXml(input: A): String 
} 

この例では、私は、XML型の代わりにStringを使用しています。この定義を使用して、我々はいくつかの簡単な型クラスのインスタンスを定義することができます。上記のコードで

implicit val stringWritableLike: WritableLike[String] = new WritableLike[String] { 
    def toXml(input: String): String = s"<text>$input</text>" 
} 

implicit val intWritableLike: WritableLike[Int] = new WritableLike[Int] { 
    def toXml(input: Int): String = s"<integer>$input</integer>" 
} 

を、私はStringInt種類の型クラスのインスタンスを定義しました。両方のインスタンスは、メソッドtoXmlの入力を、変換を提供する別の関数に頼ることなくXML文字列に変換する方法を直接定義します。つまり、型のいずれかに型クラスを適用すると、値に関係なく常に同じ変換が使用されます。

特定の値の変換をオーバーロードしたい場合、それらの値を他の型でラップし、型の型クラスインスタンスを提供したいとします。

case class Name(name: String) extends AnyVal 

implicit val nameWritableLike: WritableLike[Name] = new WritableLike[Name] { 
    def toXml(input: Name): String = s"<name>${input.name}</name>" 
} 

ここでは、名前を表すためのケースクラスと、名前をXMLに変換するための特定のタイプクラスを定義します。

これらのインスタンスを値として定義しましたが、defまたはobjectも使用しています。重要な部分は、これらのキーワードのどれが使用されているかにかかわらず、インスタンスが暗黙的であることです。暗黙のうちに、明示的にそれらを参照することなくこれらのインスタンスを取得できます。

valではなくdefを使用する必要がある場合があります。タイプクラスインスタンスが別のタイプクラスインスタンスに依存する場合に必要です。ここでは、例としてOptionを使用することができます。上記のコードで

implicit def optionWritableLike[A](implicit instanceForA: WritableLike[A]): WritableLike[Option[A]] = 
    new WritableLike[Option[A]] { 
    def toXml(input: Option[A]): String = input match { 
     case Some(a) => instanceForA.toXml(a) 
     case None => "<empty />" 
    } 
    } 

を、私たちはOptionタイプパラメータも型クラスのインスタンスを持っているすべての値で動作しますOptionタイプに型クラスのインスタンスを定義しました。ここでは、内部型を関数型パラメータAとして表します。 Aがわからないので、その型の型クラスインスタンスを取得する必要があります。これを実現するには、必要な正確な型クラスインスタンスの暗黙のパラメータを追加します。最後に、暗黙のインスタンスを使用して、基になる値をXMLに変換することができます。

型クラスインスタンスの暗黙のパラメータは、インスタンスと型パラメータのインスタンス間の依存関係を作成します。 typeパラメータのインスタンスが存在する場合は、それに依存するインスタンスを使用できます。そうでない場合は、インスタンスを使用できません。

あり、我々は型クラスの依存関係を宣言するために使用できる代替構文は次のとおりです。

implicit def optionWritableLike[A: WritableLike]: WritableLike[Option[A]] = 
    new WritableLike[Option[A]] { 
    def toXml(input: Option[A]): String = input match { 
     case Some(a) => implicitly[WritableLike[A]].toXml(a) 
     case None => "<empty />" 
    } 
    } 

ここでは、型パラメータ定義の一部として型クラスの依存関係を宣言します。宣言A: WritableLikeは、typeパラメータが型クラスWritableLikeの型クラスインスタンスを持つ必要があることを意味します。 Scalaのキーワードimplicitlyを使用してインスタンスにアクセスできます。implicitlyコールは少し冗長ですので、型クラスのコンパニオンオブジェクトにヘルパー関数を定義することが通例である。

object WritableLike { 
    def apply[A: WritableLike]: WritableLike[A] = implicitly[WritableLike[A]] 
} 

今、私たちはこのように型クラスのインスタンスにアクセスすることができます。WritableLike[A].toXml(a)

ヘルパーメソッドは、インスタンス定義の外で型クラスを使用するときにも役立ちます。

WritableLike[String].toXml("foobar")    // <text>foobar</text> 
WritableLike[Int].toXml(123)      // <integer>123</integer> 
WritableLike[Name].toXml(Name("Alan"))    // <name>Alan</name> 
WritableLike[Option[String]].toXml(Some("foobar")) // <text>foobar</text> 
WritableLike[Option[Int]].toXml(None)    // <empty /> 
+0

この回答が役立ちます。ご質問がありましたら、ここでコメントしてください。答えを広げることができるかどうかがわかります。 :) –

+0

こんにちはJaakoはあなたの答えに感謝します。 より幅広いコンテキストでは、いくつかのフォーマッタがあり、それぞれがケースクラス、文字列、日付、その他のnodeseqs(NodeSeqとして)の組み合わせからxml NodeSeqを生成します。空の文字列、nodeseqsまたはNoneのオプション[caseclass]があることがあります。 さまざまなxmlタグを使用してxmlドキュメントの異なる部分を生成するために、複数の場所(フォーマッタあたり)でケースクラスが使用されることがあるため、渡された関数でノードを生成する柔軟性が重要です。 –

+0

ビジネスケースはうまくいきましたが、私はScalaの型クラスパターンの力と限界を探求したかったのです。 これは言語やパターンの制限ですか? それは可能であるように感じます。 –

関連する問題