10

主に言語学習の演習として、非常に単純なテキスト テンプレート ライブラリを scala に移植することを検討しています。ライブラリは現在、Python と Javascript の両方で実装されており、その基本的な操作は多かれ少なかれ次のようになります (Python で):

template = CompiledTemplate('Text {spam} blah {eggs[1]}')
data = { 'spam': 1, 'eggs': [ 'first', 'second', { 'key': 'value' }, true ] }
output = template.render(data)

Scala でこれを行うのはそれほど難しいことではありませんが、dataパラメーターの静的な型をどのように表現するのが最適かはわかりません。

基本的に、このパラメーターには、JSON で見られる種類のものを含めることができる必要があります: いくつかのプリミティブ (文字列、整数、ブール値、null)、または 0 個以上のアイテムのリスト、または 0 個以上のアイテムのマップ。(この質問の目的のために、マップは文字列キーを持つように制限することができます。これは、とにかく Scala が好きな方法のようです。)

私の最初の考えは、Map[string, Any]a をトップレベルのオブジェクトとして使用することだけでしたが、それは私には完全に正しいとは思えません。実際、そこにあらゆる種類のクラスの任意のオブジェクトを追加したくありません。上記で概説した要素のみが必要です。同時に、Java で実際に取得できる最も近いものは だと思いますMap<String, ?>。また、Scala の作成者の 1 人が Java のジェネリックを設計したことも知っています。

私が特に興味を持っていることの 1 つは、同様の型システムを持つ他の関数型言語がこの種の問題をどのように処理するかということです。ここで本当にやりたいことは、パターン マッチングが可能な一連のケース クラスを考え出すことだと思いますが、それがどのようになるかはまったく想像できません。

私はScalaでのプログラミングを持っていますが、正直なところ、私の目は共分散/反分散のものに少し目がくらみ始めました。誰かがこれをもう少し明確かつ簡潔に説明してくれることを願っています.

4

3 に答える 3

15

データ型をモデル化するために、ある種のケース クラスが必要であることがわかります。関数型言語では、これらの種類のものは「抽象データ型」と呼ばれ、Haskell がそれらをどのように使用するかについては、少しグーグルで調べることですべて読むことができます。Scala の Haskell の ADT に相当するものは、シールされたトレイトとケース クラスを使用します。

Scala 標準ライブラリーまたは Programming in Scala book からの JSON パーサー・コンビネーターの書き直しを見てみましょう。Map[String, Any] を使用して JSON オブジェクトを表す代わりに、 Any を使用して任意の JSON 値を表す代わりに、抽象データ型 を使用して JSON 値を表しJsValueます。JsValueには、考えられる JSON 値の種類 ( 、 、 、 ( 、 )、および ) を表すいくつJsStringJsNumberJsObjectサブタイプがありJsArrayます。JsBooleanJsTrueJsFalseJsNull

この形式の JSON データの操作には、パターン マッチングが含まれます。JsValue は封印されているため、すべてのケースを処理していない場合、コンパイラは警告を発します。たとえば、を受け取り、その値の表現を返すtoJsonメソッドのコードは、次のようになります。JsValueString

  def toJson(x: JsValue): String = x match {
    case JsNull => "null"
    case JsBoolean(b) => b.toString
    case JsString(s) => "\"" + s + "\""
    case JsNumber(n) => n.toString
    case JsArray(xs) => xs.map(toJson).mkString("[",", ","]")
    case JsObject(m) => m.map{case (key, value) => toJson(key) + " : " + toJson(value)}.mkString("{",", ","}")
  }

パターン マッチングにより、すべてのケースを確実に処理できるようになり、その JsType から基になる値を「ラップ解除」することもできます。これは、すべてのケースを処理したことをタイプセーフに知る方法を提供します。

さらに、処理している JSON データの構造をコンパイル時に知っている場合は、n8han の extractors のような非常に優れた機能を実行できます。非常に強力なものです。チェックしてください。

于 2009-04-10T07:18:27.690 に答える
1

さて、これにアプローチするにはいくつかの方法があります。私はおそらく、あなたの目的のためにうまく動作するはずです(マップがではなくMap[String, Any]からのものである限り)。ただし、本当に苦労したい場合は、これに型を与えることができます。collection.immutablecollection.mutable

sealed trait InnerData[+A] {
  val value: A
}

case class InnerString(value: String) extends InnerData[String]
case class InnerMap[A, +B](value: Map[A, B]) extends InnerData[Map[A, B]]
case class InnerBoolean(value: Boolean) extends InnerData[Boolean]

dataここで、JSONフィールドを という名前の Scala フィールドに読み込むと仮定するjsDataと、そのフィールドに次の型を指定します。

val jsData: Map[String, Either[Int, InnerData[_]]

からフィールドを引き出すたびに、値がまたは( の 2 つのサブタイプ) のタイプでjsDataあるかどうかを確認して、パターン マッチを行う必要があります。内部データを取得したら、それをパターン マッチし、 、またはを表しているかどうかを判断します。Left[Int]Right[InnerData[_]]Either[Int, InnerData[_]]InnerStringInnerMapInnerBoolean

技術的には、JSON から取り出したデータを使用するには、とにかくこの種のパターン マッチングを行う必要があります。適切に型付けされたアプローチの利点は、コンパイラーがチェックして、可能性を見逃していないことを確認することです。欠点は、不可能なことをスキップできないことです('eggs'へのマッピングなどInt)。また、これらすべてのラッパー オブジェクトによってオーバーヘッドが発生するため、注意してください。

Scala では、これに必要な LoC の量を削減する型エイリアスを定義できることに注意してください。

type DataType[A] = Map[String, Either[Int, InnerData[A]]]

val jsData: DataType[_]

いくつかの暗黙的な変換を追加して API をきれいにします。

于 2009-04-08T17:22:52.883 に答える
1

JSON は、「プログラミング in Scala」のコンビネーター解析に関する章の例として使用されています。

于 2009-04-08T17:26:07.460 に答える