Scala implicit暗黙変換安全運転ガイド

8544 ワード

この短文では、暗黙的な変換の様々なシーンを実例と結びつけて説明し、まとめ、見終わった人が暗黙的な変換という大きな穴を安全に通過することを望んでいます.
暗黙変換関数
暗黙変換関数には、2つの作用シーンがあります.
  • が所望のタイプに変換される:つまり、コンパイラがXを見ると、Yが必要になると、XからYへの暗黙的な変換関数がチェックされることを意味する.
  • 2変換方法の呼び出し者:簡単に言えばobj.f()は、objオブジェクトにfメソッドがない場合、objをfメソッドを持つタイプに変換しようとする.
  • object ImpFunction extends App {
    
      class Dog(val name: String) {
        def bark(): Unit = println(s"$name say: Wang !")
      }
    
      implicit def double2int(d: Double): Int = d.toInt
    
      implicit def string2Dog(s: String): Dog = new Dog(s)
    
      val f: Int = 1.1 //       ,1.1  double2int   Int  
    
      println(f)
    
      "Teddy".bark() //         ,     string2Dog   Dog,     bark  
    
    }
    // output
    // 1
    // Teddy say: Wang !
    
    val f: Int = 1.1タイプが一致しないため、このセグメントはもともとコンパイルできませんが、コンパイラはDoubleからIntへの暗黙的な変換関数があることを発見し、暗黙的な変換を行いました."Teddy".bark() Stringタイプはもともとbarkメソッドがないが、コンパイラは、Stringをbarkメソッドを持つタイプに変換できることを発見し、string2Dog("Teddy").bark()に相当する.
    注意事項
    コンパイラは暗黙変換関数の入出力タイプのみに関心を持ち、関数名に関心を持たないため、曖昧さを避けるために、同じ役割ドメインに入出力タイプの同じ2つの暗黙変換関数があることはできません.そうしないと、コンパイラはエラーを報告します.
    暗黙クラス
    Scala 2.10は暗黙クラスという新しい特性を導入した.暗黙クラスとはimplicitキーワードで修飾されたクラスを指す.使用状況は暗黙変換関数と同様であり,クラスの構造関数を暗黙変換関数として定義し,戻りタイプがこのクラスであると見なすことができる.
    package io.github.liam8.impl
    
    object ImpClass extends App {
    
      implicit class Dog(val name: String) {
        def bark(): Unit = println(s"$name say: Wang !")
      }
    
      "Teddy".bark()
    
    }
    
    

    注意事項
    公式サイトIMPLICIT CLASSESの暗黙クラスには、次の制限があります.
  • 1は、他のtrait/クラス/オブジェクト内でのみ定義できます.
  •     object Helpers {
           implicit class RichInt(x: Int) //   !
        }
        implicit class RichDouble(x: Double) //   !
    
  • コンストラクション関数は、1つの非暗黙的なパラメータしか持ち込めません.
  •     implicit class RichDate(date: java.util.Date) //   !
        implicit class Indexer[T](collecton: Seq[T], index: Int) //   !
        implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) //   !
    

    複数の非暗黙パラメータを持つ暗黙クラスを作成できますが、暗黙変換では使用できません.
  • 3同じ役割ドメイン内では、暗黙的なクラスと同じ名前のメソッド、メンバー、またはオブジェクトは使用できません.注:これは、暗黙的なクラスがcase classではないことを意味します.
  •     object Bar
        implicit class Bar(x: Int) //   !
    
        val x = 5
        implicit class x(y: Int) //   !
    
        implicit case class Baz(x: Int) //   !
    

    暗黙パラメータ&暗黙値
    package io.github.liam8.impl
    
    object ImpParam extends App {
    
      def bark(implicit name: String): Unit = println(s"$name say: Wang !")
    
      implicit val t: String = "Hot Dog"
    
      bark
    
    }
    

    パラメータにimplicitを加えると暗黙的なパラメータとなり、暗黙的な値(変数定義にimplicitを加える)と組み合わせて使用する必要があります.最後の行のbarkにはStringタイプのパラメータが欠けています.コンパイラはStringタイプの暗黙的な値を見つけて、bark(t)を実行したことに相当します.
    implicitキーワードは関数リストのすべてのパラメータに作用し、def test(implicit x:Int, y: Double)のように関数を定義すると、xとyは暗黙的な関数になります.しかし、通常、一部のパラメータが暗黙的なパラメータであることを望んでいます.例えば、通常、すべてのデフォルト値ではなく、一部のパラメータにデフォルト値を提供し、暗黙的なパラメータはコリー化関数とともによく使用されます.これにより、最後のパラメータだけが暗黙的なパラメータ、例えばdef test(x: Int)(implicit y: Double)になることができます.
    完全な例です.
    object ImpParamWithCurry extends App {
    
      def bark(name: String)(implicit word: String): Unit = println(s"$name say: $word !")
    
      implicit val w: String = "Wang"
    
      bark("Hot Dog")
    
    }
    

    注意事項
    ***
  • 1)関数がコリー化されていない場合、implicitキーワードは関数リストのすべてのパラメータに作用します.
  • 2)暗黙的なパラメータを使用する場合、すべて指定しないか、すべて指定しないか、部分のみ指定することはできません.
  • )同じタイプの暗黙的な値は、同じ役割ドメイン内で1回しか現れない.すなわち、同じ役割ドメインで同じタイプの暗黙的な値を複数定義することはできない.
  • 4)暗黙的なパラメータを指定する場合、implicitキーワードはパラメータの先頭にのみ表示されます.
  • )パラメータの部分暗黙的なパラメータを実現するには、関数のコリー化しか使用できない、このような形式の関数を実現するには、def test(x:Int,implicity:Double)の形式で、コリー化実現:def test(x:Int)(implicity:Double)を使用する必要がある.
  • 6)コリー化関数では,implicitキーワードは最後のパラメータにのみ作用する.そうでなければ、合法ではありません.
  • )implicitキーワードは暗黙パラメータに1回しか現れず,コリー化関数も例外ではない.

  • 暗黙のオブジェクト
    暗黙的な値と同様に、暗黙的なパラメータと組み合わせて使用します.まず栗を見てみましょう(次のコードは真剣に体得する必要があります).
    package io.github.liam8.impl
    
    object ImpObject extends App {
    
      //    `   `  ,               
      trait Ordering[T] {
        //  xy  1,x==y   0.
        def compare(x: T, y: T): Int
      }
    
      //    Int      
      implicit object IntOrdering extends Ordering[Int] {
        override def compare(x: Int, y: Int): Int = {
          if (x < y) -1
          else if (x == y) 0
          else 1
        }
      }
    
      //    String      
      implicit object StringOrdering extends Ordering[String] {
        override def compare(x: String, y: String): Int = x.compareTo(y)
      }
    
      //     max  
      def max[T](x: T, y: T)(implicit ord: Ordering[T]): T = {
          if (ord.compare(x, y) >= 0) x else y
      }
    
      println(max(1, 2))
      println(max("a", "b"))
    }
    
    //output: 
    // 2
    // b
    

    max関数の役割は明らかにxとyの最大値を返すことであるが、xとyの値タイプは固定されていない.maxはxとyの大型をどのように比較するか分からないので、暗黙的なパラメータimplicit ord: Ordering[T]を定義し、Ordering[T]タイプのソート器にxとyの比較を支援することを望んでいる.max(1, 2)が呼び出されると、コンパイラはOrdering[Int]タイプのパラメータが必要であることを発見し、implicit object IntOrderingは暗黙的なオブジェクトが要求に合致することを定義し、max関数を入力するために使用される.
    暗黙オブジェクトは、上の暗黙値と非常に似ていますが、タイプが特殊です.
    Scalaでscala.math.Orderingはよく使われる内蔵特質で、このコードを理解すれば、Orderingの原理も大体理解できます.
    コンテキスト定義(context bounds)
    これは暗黙的なパラメータの文法糖です.
    上の暗黙的なオブジェクトの例を見て、min関数を追加すると、大体こうなります.
      def min[T](x: T, y: T)(implicit ord: Ordering[T]): T = {
        if (ord.compare(x, y) >= 0) y else x
      }
    

    しかしmaxとmin関数のパラメータが長いため,簡略化された書き方が現れた.
      def min[T: Ordering](x: T, y: T): T = {
        val ord = implicitly[Ordering[T]]
        if (ord.compare(x, y) >= 0) y else x
      }
    
    [T: Ordering]この構文はコンテキスト定義と呼ばれ、コンテキストにはmin関数に入力されるOrdering[T]タイプの暗黙的な値が必要であることを意味する.しかし、この暗黙的な値は変数に明確に与えられていないため、直接使用することはできません.implicitly関数が暗黙的な値を取り出す必要があります.
    implicitly関数の定義は非常に簡単で、Tタイプの隠し値を返す役割を果たします.
    @inline def implicitly[T](implicit e: T) = e
    

    しかい
    この文法は廃棄されていますが、簡単に説明することができます.
    def min[T  y) y else x
    }
    

    視野の定義T T Ordered[T], x > yはコンパイルすることができる.
    の き は は のようなものなので、 の が えなくても です.
      def min[T](x: T, y: T)(implicit c: T => Ordered[T]): T = {
        if (x > y) y else x
      }
    

    メカニズム
  • タグルール:implicitとタグ けされた のみが です.
  • ドメイン : された な は、 の として ドメインに するか、 のソースまたはターゲットタイプに けられている があります.

  • は、someVariableとして することができないことを する.convert(x)の は、convert(x)のみです. ルールには があり、コンパイラはソースタイプまたは の のターゲットタイプの オブジェクトで な を します.
    ちょっとわかりにくい? を て!
    package io.github.liam8.impl
    
    object ImpCompObject extends App {
    
      object Dog {
        implicit def dogToCat(d: Dog) = new Cat(d.name)
      }
    
      class Cat(val name: String) {
        def miao(): Unit = println(s"$name say: Miao !")
      }
    
      class Dog(val name: String) {
        def bark(): Unit = println(s"$name say: Wang !")
      }
    
      new Dog("Teddy").miao()
    
    }
    //Teddy say: Miao !
    

    の ドメインには な は されず されていますが、Dogの オブジェクトで つかったので、DogはCatに できます.これはコンテキストとは なく、Dogがスキルを っています.
  • な はありません. な は、 の が しない り です.
  • び しルール: な は1つのみ みられます.
  • が されます. されたコードタイプが しくチェックされている 、 な は みられません.

  • を する.
  • タイプがターゲットタイプと しない
  • オブジェクト び しクラスに しないメソッドまたはメンバーの
  • パラメータがない
  • すなわち、 な が なのは、 のタイプに し、 び し を する 、 なパラメータの3つである.
    へんかんきこう
    この はScalaの な を く することから ます
    すなわち、コンパイラが した をどのように するかについて、 には の2つのルールがあります.
  • 1.まず、 のコードの で、 エンティティ( メソッド クラス オブジェクト)
  • が されます.
  • 2. 1の が エンティティの に した 、 パラメータのタイプの ドメインでタイプの ドメインを し けることは、そのタイプに けられたすべての モジュールを し、1つの エンティティのタイプTの は の りである.
  • 1 TがT with A with B with Cと されている 、A,B,CはいずれもTの であり、Tの の で、それらの オブジェクトは
  • される.
  • 2 Tがパラメトリックタイプである 、タイプパラメータおよびタイプパラメータに けられた はTの として され、 えば、List[String]の は、Listの オブジェクトおよびStringの オブジェクト
  • を する.
  • Tが のタイプのp.Tである、すなわち、Tがあるpオブジェクト に する 、このpオブジェクトも
  • で される.
  • 4 TがタイプインジェクションS#Tである 、SとTはともに
  • される.

    つ に
    この は「Scalaプログラミング」から ています
    な が に されると、コードが になります.したがって、 しい な を することを する に、 、 せの 、またはメソッドの ロードなどの の で、 じ を できるかどうかをまず してください.これらがすべて せず、コードに さと があると じたら、 な がちょうど に つかもしれません.
    だから... に して、 に して、good luck!

    IMPLICIT CLASSES
    ***
    『Scalaプログラミング』
    Scalaの な を く する
    コード
    Github
    は の を してください.https://liam-blog.ml/2019/09/28/scala-implicit/
    その のブロガー を