Scalaで暗黙的に使用して心を留める方法

画像







Scalaは表現力豊かな手段を豊富に備えており、古典的なOOP言語の経験豊富なプログラマーはこれを好まないでしょう。 暗黙的なパラメーターと変換は、この言語の最も物議を醸す機能の1つです。 「暗示的」という言葉は、いわば、自明ではなく混乱を招くものを暗示しています。 それにもかかわらず、それらと友達になると、暗黙的なものは、コードの量を減らすことからコンパイル時に多くのチェックを行う能力まで、幅広い可能性を開きます。







私は彼らと一緒に仕事をした経験を共有し、公式ドキュメントと開発ブログがまだ何について沈黙しているかについて話したいです。 すでにScalaに精通している場合、暗黙的なパラメーターを使用しようとしたが、それらを操作する際にいくつかの問題が発生する場合、または少なくともそれらについて聞いた場合、この投稿は興味があるかもしれません。









内容:









implicitキーワードは、Scalaの3つの概念、暗黙的なパラメーター、暗黙的な変換、および暗黙的なクラスを指します。







暗黙的なパラメーター



暗黙的なパラメーターは、呼び出しのコンテキストから関数に自動的に転送できるパラメーターです。 これを行うには、対応するタイプの変数を一意に定義し、暗黙的なキーワードでマークする必要があります。







def printContext(implicit ctx: Context) = println(ctx.name) implicit val ctx = Context("Hello world") printContext
      
      





出力されます:







 Hello world
      
      





printContextメソッドでは、暗黙的にContext型の変数を取得し、その名前フィールドの内容を出力します。 まだ怖くない。







暗黙的なパラメーターを解決するメカニズムは、ジェネリック型をサポートしています。







 case class Context[T](message: String) def printContextAwared[T](x: T)(implicit ctx: Context[T]) = println(s"${ctx.message}: $x") implicit val ctxInt = Context[Int]("This is Integer") implicit val ctxStr = Context[String]("This is String") printContextAwared(1) printContextAwared("string")
      
      





出力されます:







 This is Integer: 1 This is String: string
      
      





このコードは、最初のケースでctxIntパラメーターを、2番目のケースでctxStringをprintContextAwaredメソッドに明示的に渡した場合と同等です。







 printContextAwared(1)(ctxInt) printContextAwared("string")(ctxStr)
      
      





興味深いことに、暗黙的なパラメーターはフィールドである必要はなく、メソッドでもかまいません。







 implicit def dateTime: LocalDateTime = LocalDateTime.now() def printCurrentDateTime(implicit dt: LocalDateTime) = println(dt.toString) printCurrentDateTime Thread.sleep(1000) printCurrentDateTime
      
      





出力されます:







 2017-05-27T16:30:49.332 2017-05-27T16:30:50.476
      
      





さらに、暗黙的な関数パラメーターは、暗黙的なパラメーターを受け入れることができます。







 implicit def dateTime(implicit zone: ZoneId): ZonedDateTime = ZonedDateTime.now(zone) def printCurrentDateTime(implicit dt: ZonedDateTime) = println(dt.toString) implicit val utc = ZoneOffset.UTC printCurrentDateTime
      
      





出力されます:







 2017-05-28T07:07:27.322Z
      
      





暗黙的な変換



暗黙的な変換により、あるタイプの値を別のタイプに自動的に変換できます。

暗黙的な変換を指定するには、1つの明示的な引数から関数を定義し、暗黙的なキーワードでマークする必要があります。







 case class A(i: Int) case class B(i: Int) implicit def aToB(a: A): B = B(ai) val a = A(1) val b: B = a println(b)
      
      





出力されます:







 B(1)
      
      





暗黙のパラメーター関数に当てはまるものはすべて暗黙の変換にも当てはまります。一般化された型がサポートされ、明示的に1つだけ存在する必要がありますが、任意の数の暗黙のパラメーターなどがあります







 case class A(i: Int) case class B(i: Int) case class PrintContext[T](t: String) implicit def aToB(a: A): B = B(ai) implicit val cContext: PrintContext[B] = PrintContext("The value of type B is") def printContextAwared[T](t: T)(implicit ctx: PrintContext[T]): Unit = println(s"${ctx.t}: $t") val a = A(1) printContextAwared[B](a)
      
      





制限事項

Scalaでは、複数の暗黙的な変換を連続して使用することは許可されていないため、コードは次のようになります。







 case class A(i: Int) case class B(i: Int) case class C(i: Int) implicit def aToB(a: A): B = B(ai) implicit def bToC(b: B): C = C(bi) val a = A(1) val c: C = a
      
      





コンパイルされません。

それでも、すでに見たように、Scalaはチェーン内の暗黙的なパラメーターの検索を禁止していないため、このコードを次のように修正できます。







 case class A(i: Int) case class B(i: Int) case class C(i: Int) implicit def aToB(a: A): B = B(ai) implicit def bToC[T](t: T)(implicit tToB: T => B): C = C(ti) val a = A(1) val c: C = a
      
      





関数が暗黙的な値をとる場合、その本体では暗黙的な値または変換として表示されることに注意してください。 前の例では、bToCメソッドを宣言するために、tToBは暗黙的なパラメーターであり、同時に、メソッド内で暗黙的な変換として既に機能しています。







暗黙のクラス



クラス宣言の前の暗黙的なキーワードは、コンストラクター引数の値の特定のクラスへの暗黙的な変換を記述する、よりコンパクトな形式です。







 implicit class ReachInt(self: Int) { def fib: Int = self match { case 0 | 1 => 1 case i => (i - 1).fib + (i - 2).fib } } println(5.fib)
      
      





出力されます:







 5
      
      





暗黙のクラスは、機能とクラスを混合する方法のように思えるかもしれませんが、実際にはこの概念はやや広範です。







 sealed trait Animal case object Dog extends Animal case object Bear extends Animal case object Cow extends Animal case class Habitat[A <: Animal](name: String) implicit val dogHabitat = Habitat[Dog.type]("House") implicit val bearHabitat = Habitat[Bear.type]("Forest") implicit class AnimalOps[A <: Animal](animal: A) { def getHabitat(implicit habitat: Habitat[A]): Habitat[A] = habitat } println(Dog.getHabitat) println(Bear.getHabitat) // : //println(Cow.getHabitat)
      
      





出力されます:







 Habitat(House) Habitat(Forest)
      
      





ここでは、暗黙的なAnimalOpsクラスで、適用される値のタイプがAとして表示されることを宣言し、getHabitatメソッドでは、暗黙的なパラメーターHabitat [A]が必要です。 存在しない場合、Cowと同様に、コンパイルエラーが発生します。







暗黙のクラスの助けがなければ、 F境界のポリモーフィズムは同じ効果を達成するのに役立ちます。







 sealed trait Animal[A <: Animal[A]] { self: A => def getHabitat(implicit habitat: Habitat[A]): Habitat[A] = habitat } trait Dog extends Animal[Dog] trait Bear extends Animal[Bear] trait Cow extends Animal[Cow] case object Dog extends Dog case object Bear extends Bear case object Cow extends Cow case class Habitat[A <: Animal[A]](name: String) implicit val dogHabitat = Habitat[Dog]("House") implicit val bearHabitat = Habitat[Bear]("Forest") println(Dog.getHabitat) println(Bear.getHabitat)
      
      





ご覧のとおり、この場合、Animal型は宣言を非常に複雑にしており、追加の再帰パラメーターAが登場しました。これは、サービスのみの役割を果たします。 これは紛らわしいです。







暗黙的なパラメーターチェーン



この質問は公式のFAQ http://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.htmlで対処されています。

暗黙的な変換に関するセクションで述べたように、コンパイラは暗黙的な変換を再帰的に適用することはできません。 ただし、暗黙的なパラメーターの再帰的な解決はサポートしています。







以下の例では、対応する型クラスが暗黙的に定義されている型、describeメソッドを追加します。describeメソッドは、ある種の人間の言語で説明を返します(ご存じのとおり、JVMの実行時に正確な型を決定することは不可能なので、コンパイルで定義します) -時間):







 sealed trait Description[T] { def name: String } case class ContainerDescr[P, M[_]](name: String) (implicit childDescr: Description[P]) extends Description[M[P]] { override def toString: String = s"$name of $childDescr" } case class AtomDescr[P](name: String) extends Description[P] { override def toString: String = name } implicit class Describable[T](value: T)(implicit descr: Description[T]) { def describe: String = descr.toString } implicit def listDescr[P](implicit childDescr: Description[P]): Description[List[P]] = ContainerDescr[P, List]("List") implicit def arrayDescr[P](implicit childDescr: Description[P]): Description[Array[P]] = ContainerDescr[P, Array]("Array") implicit def seqDescr[P](implicit childDescr: Description[P]): Description[Seq[P]] = ContainerDescr[P, Seq]("Sequence") implicit val intDescr = AtomDescr[Int]("Integer") implicit val strDescr = AtomDescr[String]("String") println(List(1, 2, 3).describe) println(Array("str1", "str2").describe) println(Seq(Array(List(1, 2), List(3, 4))).describe)
      
      





出力されます:







 List of Integer Array of String Sequence of Array of List of Integer
      
      





説明は基本タイプです。

ContainerDescrは再帰的なクラスであり、記述されたコンテナのタイプの暗黙的なDescriptionパラメーターの存在を必要とします。

AtomDescrは、単純型を記述するターミナルクラスです。







画像

暗黙的なパラメーターを解決するためのスキーム。







暗黙的なパラメーターのデバッグ



暗黙的なパラメータのチェーンを使用して開発する場合、時々、曖昧な暗黙的な値と発散する暗黙的な拡張という、やや曖昧な名前のコンパイル時エラーが発生することがあります。 コンパイラがあなたに何を望んでいるかを理解するには、これらのメッセージの意味を理解する必要があります。







あいまいな暗黙の値



通常、このエラーは、同じスコープ内に適切なタイプのいくつかの競合する暗黙的な値が存在することを意味し、コンパイラーはどちらを優先するかを決定できません(暗黙的なパラメーターの検索でコンパイラーがスコープを通過する順序は、 この回答にあります )。







 implicit val dog = "Dog" implicit val cat = "Cat" def getImplicitString(implicit str: String): String = str println(getImplicitString)
      
      





このコードをコンパイルしようとすると、エラーが発生します。







 Error:(7, 11) ambiguous implicit values: both value dog in object Example_ambigous of type => String and value cat in object Example_ambigous of type => String match expected type String println(getImplicitString)
      
      





これらの問題は明らかに解決されています-コンパイラが一意に決定できるように、このタイプの暗黙的なパラメータをコンテキストに1つだけ残す必要があります。







暗黙的な拡張の多様化



このエラーは、暗黙的な値を探すときに無限再帰を意味します。







 implicit def getString(implicit str: String): String = str println(getString)
      
      





エラーは次のとおりです。







 Error:(5, 11) diverging implicit expansion for type String starting with method getString in object Example_diverging println(getString)
      
      





この種のエラーは追跡が困難です。 再帰に終端ブランチがあることを確認してください。 多くの場合、パラメータチェーン全体を明示的に置き換えて、このコードがコンパイルされるようにすることが役立ちます。







コンパイラフラグのログの暗黙的



コンパイラフラグ-Xlog-implicits



も使用してみてください。これを使用すると、scalacは暗黙的なパラメーターを解決する手順と失敗の原因を記録します。







画像

暗黙的なパラメーターの候補に関するコンパイラメッセージ。







@ImplicitNotFoundアノテーション



@implicitNotFoundアノテーションを使用してクラスと特性をマークすると、このタイプの暗黙的な値がより人道的でないことがコンパイラメッセージに示されます。







 @implicitNotFound("No member of type class NumberLike in scope for ${T}") trait NumberLike[T] { def plus(x: T, y: T): T def divide(x: T, y: Int): T def minus(x: T, y: T): T }
      
      





暗黙的なパラメーターを宣言する手順



この側面の説明はインターネット上で見つけることができず、実験的に明らかにする必要がありました。







暗黙的な関数パラメーターを宣言する手順は、基本的に重要です。







これは、いくつかの暗黙的なパラメーターを使用して、他のパラメーターの可視性を制限できることを意味します。 たとえば、スコープに適切なタイプの2つの値があり、目的のタイプを明確にすることなく、そのうちの1つを選択する必要がある場合。







 sealed trait BaseSought class Target extends BaseSought class Alternative extends BaseSought trait Searchable[T <: BaseSought] implicit def search[T <: BaseSought](implicit canSearch: Searchable[T], sought: T): T = sought implicit val target = new Target() implicit val alt = new Alternative() implicit val canSearchTarget = new Searchable[Target] {} search // : Target
      
      





タイプ[T <:BaseSought]に適したスコープ内の2つのパラメーターがありますが、暗黙的なSearchable [T]パラメーターはそのうちの1つに対してのみ定義されているため、明確に決定でき、コンパイルエラーは発生しません。







画像

暗黙的なパラメーターの解決に成功しました。







暗黙的なパラメーターを異なる順序で定義した場合:







 implicit def search[T <: BaseSought](implicit sought: T, canSearch: Searchable[T]): T = sought
      
      





エラーが発生します:







 Error:(17, 1) ambiguous implicit values: both value target in object Example11 of type => Example11.Target and value alt in object Example11 of type => Example11.Alternative match expected type T search // : Target
      
      





画像

おっと...







おわりに



結論として、私はコメントで必然的に尋ねられる質問にすぐに答えたいと思います:「なぜ私たちはそのような困難を必要とするのでしょうか?







はい、必要ありません。 間違いなく、コードを助けてコードをより複雑にしたい場合、含意は必要ありません。 たとえば、Javaなどの言語の後、プログラマは、言語に多くのツールがある場合、それらすべてを使用する必要があると考えます。 実際、複雑なタスクにのみ洗練されたツールを使用する必要があります。







意味なしに問題を美しく解決できるなら、そうしなければ、やり直してください。







同僚がツールを習得するのにかなりの時間がかかることを理解している場合でも、ここでそれなしでは実行できず、その範囲を制限し、シンプルなインターフェイスでライブラリを作成し、その品質を確保します。 そして、人々は、その中の何かを変えるために頭に浮かんだ瞬間に、自分自身で成長します。







画像








All Articles