Scalaプログラミング(第19章:タイプパラメータ化)

4169 ワード

1.関数キュー:純粋な関数キューは、完全に永続化されたデータ構造と呼ばれ、拡張または変更された後も古いバージョンが使用され続けます.
trait Queue[+T] {
  def head: T
  def tail: Queue[T]
  def enqueue[U>:T](x: U): Queue[U]
}

object Queue {
  def apply[T](xs: T*): Queue[T] = new QueueImpl(xs.toList, Nil)

  private class QueueImpl[T](
    private[this] var leading: List[T],
    private[this] var trailing: List[T]
  ) extends Queue[T] {
    private def mirror() =
      if (leading.isEmpty) {
        while (!trailing.isEmpty) {
          leading = trailing.head :: leading
          trailing = trailing.tail
        }
      }

    def head: T = {
      mirror()
      leading.head
    }

    def tail: QueueImpl[T] = {
      mirror()
      new QueueImpl(leading.tail, trailing)
    }

    def enqueue[U>:T](x: U):Queue[U]= new QueueImpl(leading, x :: trailing)
  }

}

パラメータリストの前にprivate修飾子を付けることで、プライマリ構築メソッドを非表示にできます.このような初期要素のセットからキューを構築するためにファクトリメソッドを追加します.
 
2.型変注記:Queueはタイプではなく特質です.Queueはタイプではありません.タイプパラメータを受信するからです.したがって,Queueは特質であり,Queue[String]はタイプである.Queueはタイプ構造法とも呼ばれる.
タイプパラメータの前に+を加えてサブタイプ関係を表すと,このパラメータではコヒーレント(柔軟)である.この文字によって、Scalaに私たちが求めている効果は、Queue[String]がQueue[AnyRef]のサブタイプであることを示します.
trait Queue[+T]{...}

+に加えて、逆のサブタイプ関係を表す接頭辞として使用できます.TがタイプSのサブタイプである場合、Queue[S]がQueue[T]のサブタイプであることを示す.
trait Queue[-T]{...}

タイプパラメータがコヒーレントであるか、インバータであるか、不変であるかは、タイプパラメータの型変と呼ばれる.タイプパラメータの横に置くことができる+と-は、型変注釈と呼ばれます.
 
3.チェック型変注解:型変注解の正確性を検証するために、Scalaコンパイラはクラスまたは特質定義のすべてのタイプパラメータが現れる点をコヒーレント、インバータ、および不変に分類します.「点」とは、クラスまたは特質のいずれかがタイプパラメータで使用できる場所を指す.
コンパイラはクラスのタイプパラメータの使用をチェックします.+注記のタイプパラメータはコヒーレントポイントでのみ使用できますが、-注記のタイプパラメータはインバータポイントでのみ使用できます.型変注解のないタイプパラメータは、タイプパラメータが現れる任意の点で使用できるため、不変点で使用できる唯一のタイプパラメータでもある.
簡単な理解では、メソッドのタイプパラメータと値パラメータはインバータポイントにあり、注記-、注記を使用しないかのいずれかです.メソッドの戻り値は、コメント+またはコメントを使用しない共変点にあります.(正しくないかもしれませんが、方向を提供します.間違っているかもしれませんが)
 
4.下限:Queueクラスに戻ると、enqueueメソッドのパラメータタイプは逆変換点であり、Tはコヒーレントである.複数の状態でenqueueを汎化(enqueueメソッド自体にタイプパラメータを与える)し、そのタイプパラメータに下限を使用する方法があります.
def enqueue[U>:T](x: U):Queue[U]= new QueueImpl(leading, x :: trailing)

新しく定義されたタイプパラメータUは、型変注記がなく、任意の点で使用することができる.U>:Tという構文でUの下限をTと定義する.そうすると、UはTのスーパータイプでなければなりません.現在enqueueのパラメータタイプはTではなくUであり,メソッドの戻り値はQueue[T]ではなくQueue[U]である.
例えば、1つのFruitクラスと2つのサブクラスAppleとOrangeがあると仮定する.Queueクラスの定義に従ってQueue[Apple]にOrangeを追加することができ、その結果Queue[Fruit]となる.
 
5.インバータ:ある場面でインバータは自然です.出力チャネル特質:
trait OutputChannel[-T]{
  def write(x:T)
}

ここでのOutputChannelはT逆変換として定義される.したがって,1つのAnyRefの出力チャネルはStringの出力チャネルのサブクラスである.直感に反するように見えるが、実際には通じる.私たちはOutputChannel[String]に何をすることができますか?唯一サポートされている操作はStringを書くことです.同様の操作で、1つのOutputChannel[AnyRef]も完了する.したがって、OutputChannel[String]を1つのOutputChannel[AnyRef]で安全に置き換えることができる.これに対し、OutputChannel[AnyRef]が必要な場所でOutputChannel[String]に置き換えるのは安全ではありません.結局、任意のオブジェクトをOutputChannel[AnyRef]に渡すことができ、OutputChannel[String]はすべての書かれた値が文字列であることを要求する.
上記の推論は、タイプUの値が必要な場所でタイプTの値を置き換えることができれば、タイプTがタイプUのサブタイプであると安全に仮定できるタイプシステム設計の一般的な原則を指す.これは李氏置換の原則と呼ばれている.
コヒーレントとインバータが同じタイプに同時に現れる場合があります.顕著な例はScalaの関数特質である.たとえば、関数タイプA=>Bを書くと、ScalaはそれをFunction[A,B]に展開します.標準クラスライブラリのFunction 1では、コヒーレントとインバータが同時に使用されます.
trait Function1[ -T1, +R]{
  def apply(v1: T1): R
}

例を挙げます.
class Food(val f:String)
class Grass(f:String) extends Food(f)

class Cow{
  def eat(food: Grass=>AnyRef): Unit ={}
  def info(x:Food)=x.f
  //     Food=>String     
  eat(info)
}

 
6.オブジェクトのプライベートデータ:
private class QueueImpl[T](
    private[this] var leading: List[T],
    private[this] var trailing: List[T]
  ) extends Queue[T] {

このコードがScalaのタイプチェックに合格できるかどうか疑問に思うかもしれません.結局、キューには、2つのコヒーレントなパラメータタイプTの再割り当て可能なフィールドが含まれている.これは型変のルールに違反しているのではないでしょうか.確かにこの疑いはあるが、leadingとtrailingはprivate[this]の修飾子を持っているため、オブジェクトはプライベートであり、定義されたオブジェクトの内部からしかアクセスできない.定義された変数の同じオブジェクトからこれらの変数にアクセスしても、型変の問題は発生しません.直感的には、タイプ変更がタイプエラーを引き起こすシーンを構築するには、オブジェクトを定義するよりも静的タイプの方が弱いオブジェクトを参照する必要がありますが、オブジェクトのプライベート値にアクセスする場合は不可能です.
 
7.上界:構文は「T<:ordered="」>