Scalaでの暗黙の理解


308

私はScalaのプレイフレームワークチュートリアルを進めていて、このコードスニペットに出くわしました。

def newTask = Action { implicit request =>
taskForm.bindFromRequest.fold(
        errors => BadRequest(views.html.index(Task.all(), errors)),
        label => {
          Task.create(label)
          Redirect(routes.Application.tasks())
        } 
  )
}

だから私は調査することに決め、この投稿に出くわしました

まだわかりません。

これの違いは何ですか:

implicit def double2Int(d : Double) : Int = d.toInt

そして

def double2IntNonImplicit(d : Double) : Int = d.toInt

明白な事実以外は、メソッド名が異なります。

いつ使用する必要がありますimplicitか?その理由は?


このチュートリアルは本当に便利だと思いました:Scalaチュートリアル-暗黙的な関数の作成方法を学ぶ
Adrian Moisa

回答:


391

暗黙の主な使用例を以下で説明しますが、詳細については、「Scalaでのプログラミング」の関連する章を参照してください。

暗黙的なパラメーター

メソッドの最後のパラメーターリストにはマークを付けることができますimplicit。これは、値が呼び出されたコンテキストから値が取得されることを意味します。スコープ内に正しい型の暗黙の値がない場合、コンパイルされません。暗黙的な値は単一の値に解決され、衝突を回避する必要があるため、型をその目的に固有のものにすることをお勧めします。たとえば、メソッドで暗黙的な値を見つける必要はありません。Int

例:

  // probably in a library
class Prefixer(val prefix: String)
def addPrefix(s: String)(implicit p: Prefixer) = p.prefix + s

  // then probably in your application
implicit val myImplicitPrefixer = new Prefixer("***")
addPrefix("abc")  // returns "***abc"

暗黙的な変換

コンパイラーは、コンテキストに対して誤ったタイプの式を検出するFunctionと、タイプチェックを可能にするタイプの暗黙的な値を探します。したがって、Aが必要で、が見つかったB場合はB => A、スコープ内でtypeの暗黙的な値を探します(BおよびAオブジェクトなどの他の場所が存在する場合は、それらもチェックします)。defsはFunctionオブジェクトに「eta展開」できるため、implicit def xyz(arg: B): A willも同様にできます。

したがって、2つのメソッドの違いは、マークimplicitされたものDoubleは、が見つかったときにコンパイラによって挿入されるということIntです。

implicit def doubleToInt(d: Double) = d.toInt
val x: Int = 42.0

と同じように動作します

def doubleToInt(d: Double) = d.toInt
val x: Int = doubleToInt(42.0)

2番目では、変換を手動で挿入しました。最初に、コンパイラは同じことを自動的に行いました。左側に型注釈があるため、変換が必要です。


Playからの最初のスニペットについて:

アクションについては、このページのPlayドキュメントから説明していますAPIドキュメントもご覧ください)。使用しています

apply(block: (Request[AnyContent])Result): Action[AnyContent]

Action(同じ名前の特色に仲間です)オブジェクト。

したがって、引数として関数を指定する必要があります。これは、次の形式でリテラルとして記述できます。

request => ...

関数リテラルでは、の前の部分=>が値の宣言でありimplicit、他のval宣言と同様に、必要に応じてマークを付けることができます。ここでrequest implicit、タイプチェックのためにこれをマークする必要はありませんが、そうすることで、関数内で必要になる可能性のあるすべてのメソッドの暗黙的な値として利用できます(もちろん、明示的に使用することもできます)。 。この特定のケースではbindFromRequestFormクラスのメソッドに暗黙のRequest引数が必要なため、これが行われています。


12
ご回答ありがとうございます。21章のリンクは本当に素晴らしいです。感謝します。
クライヴ

14
ただ、これを追加するには、以下のビデオでは、暗黙の優れた解説を加えたScalaの他のいくつかの機能を提供しますyoutube.com/watch?v=IobLWVuD-CQ
シャクティ

上のビデオで24:25にジャンプ(55分間聴きたくない人向け)
パピギー

36

警告:皮肉が慎重に含まれています!YMMV ...

ルイージの答えは完全で正しいです。これは、Scalaプロジェクトで非常に頻繁に発生する、暗黙的に暗黙のうちに見事に使い過ぎる方法の例で少し拡張するためのものです。実際には非常に頻繁に、「ベストプラクティス」ガイドの1つでそれを見つけることもできます。

object HelloWorld {
  case class Text(content: String)
  case class Prefix(text: String)

  implicit def String2Text(content: String)(implicit prefix: Prefix) = {
    Text(prefix.text + " " + content)
  }

  def printText(text: Text): Unit = {
    println(text.content)
  }

  def main(args: Array[String]): Unit = {
    printText("World!")
  }

  // Best to hide this line somewhere below a pile of completely unrelated code.
  // Better yet, import its package from another distant place.
  implicit val prefixLOL = Prefix("Hello")
}

1
はは。いいユーモアのセンス。
デット

1
ユーモアに感謝します。このようなことは、私が何年も前にScalaを学ぶことをやめた理由の1つであり、今だけ戻ってきました。私が見ているコードの中で暗黙のうちのいくつか(多く)がどこから来ているのか私は確信がありませんでした。
メルストン

7

requestパラメータをimplicit次のようにマークする理由とタイミング:

アクションの本文で使用するいくつかのメソッドには、たとえば、Form.scalaがメソッドを定義するような、暗黙のパラメーターリストがあります。

def bindFromRequest()(implicit request: play.api.mvc.Request[_]): Form[T] = { ... }

呼び出すだけなので、必ずしもこれに気付く必要はありませんmyForm.bindFromRequest()。暗黙の引数を明示的に指定する必要はありません。いいえ、リクエストのインスタンスを必要とするメソッド呼び出しに遭遇するたびに渡される有効な候補オブジェクトを探すようコンパイラーに任せます。利用可能なリクエストがあるので、あなたする必要があるのはそれをimplicit

あなたは明示的に利用可能としてマーク暗黙の使用。

Playフレームワークから送信されたリクエストオブジェクト(「リクエスト」という名前を付けたが、「r」または「req」のみを使用することもできる)を必要に応じて「sly」で使用しても「OK」であることをコンパイラーに示唆する。

myForm.bindFromRequest()

見える?そこにはありませんが、ありますが!

それはちょうどあなたが(しかし、あなたはそれが必要なのは、すべての場所に手動でそれをスロットにせずに起こることができます願い、それがマークされていますならばどんなにそうならば、明示的に渡すimplicitかどうか):

myForm.bindFromRequest()(request)

暗黙のマークを付けない場合は、上記実行する必要があります。暗黙のマークを付ける必要はありません。

リクエストをimplicitいつマークする必要がありますか?Requestのインスタンスが必要な暗黙のパラメーターリストを宣言するメソッドを使用している場合にのみ、本当に必要です。ただし、単純にするために、implicit 常にリクエストにマークを付ける習慣をつけることができます。そうすれば、美しい簡潔なコードを書くことができます。


2
「そのようにすれば、美しい簡潔なコードを書くことができます。」または、@ DanielDinnyesが指摘するように、美しく難読化されたコード。インプリシットがどこから来ているのかを追跡するのは本当に大変なことかもしれませんし、注意しないとコードが実際に読みにくくなり、メンテナンスが難しくなります。
メルストン

7

scalaでは暗黙的に次のように動作します:

コンバータ

パラメーター値インジェクター

Implicitの使用には3つのタイプがあります

  1. 暗黙的な型変換:エラー生成割り当てを意図した型に変換します

    val x:String = "1"

    val y:Int = x

StringIntサブタイプではないため、2行目でエラーが発生します。エラーを解決するために、コンパイラーは、暗黙のキーワードを持ち、引数としてStringを取り、Intを返すスコープ内でそのようなメソッドを探します

そう

implicit def z(a:String):Int = 2

val x :String = "1"

val y:Int = x // compiler will use z here like val y:Int=z(x)

println(y) // result 2  & no error!
  1. 暗黙的なレシーバー変換:通常、レシーバー呼び出しオブジェクトのプロパティによって、たとえば メソッドまたは変数。したがって、レシーバーが任意のプロパティを呼び出すには、プロパティがそのレシーバーのクラス/オブジェクトのメンバーである必要があります。

    class Mahadi{
    
    val haveCar:String ="BMW"
    
    }

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

ここでmahadi.haveTvはエラーを生成します。Scalaコンパイラーはまず、マハディレシーバーのhaveTvプロパティを探します。見つかりません。次に、Mahadiオブジェクトを引数として取り、Johnnyオブジェクトを返す暗黙のキーワードを持つスコープ内のメソッドを探します。しかし、ここにはありません。したがって、エラーが発生します。しかし、以下は大丈夫です。

class Mahadi{

val haveCar:String ="BMW"

}

class Johnny{

val haveTv:String = "Sony"

}

val mahadi = new Mahadi

implicit def z(a:Mahadi):Johnny = new Johnny

mahadi.haveTv // compiler will use z here like new Johnny().haveTv

println(mahadi.haveTv)// result Sony & no error
  1. 暗黙的なパラメータインジェクション:メソッドを呼び出し、そのパラメータ値を渡さないと、エラーが発生します。Scalaコンパイラーはこのように機能します。最初に値を渡そうとしますが、パラメーターの直接の値は取得しません。

    def x(a:Int)= a
    
    x // ERROR happening

2番目のパラメータは、それがどんな探します暗黙のキーワードがある場合はヴァルスコープ持っている同じタイプ値のを。取得できない場合、エラーが発生します。

def x(implicit a:Int)= a

x // error happening here

パラメータa暗黙のキーワードがあるため、この問題を解決するために、コンパイラはIntタイプを持つ暗黙のvalを探します。

def x(implicit a:Int)=a

implicit val z:Int =10

x // compiler will use implicit like this x(z)
println(x) // will result 10 & no error.

もう一つの例:

def l(implicit b:Int)

def x(implicit a:Int)= l(a)

次のように書くこともできます

def x(implicit a:Int)= l

ためlは有する暗黙パラメータとの範囲に方法Xの身体、ある暗黙的なローカル変数パラメータがローカル変数である)のパラメータであり、xは、そうでのxの本体方法メソッドシグネチャLの暗黙の引数の値は、あります提出されたXメソッドのローカル暗黙の変数(パラメータ)暗黙のうちに a

そう

 def x(implicit a:Int)= l

このようなコンパイラになります

def x(implicit a:Int)= l(a)

もう一つの例:

def c(implicit k:Int):String = k.toString

def x(a:Int => String):String =a

x{
x => c
}

ので、エラーの原因となるCX {X => C}は、引数または暗黙ヴァルに明示的に価値の通過を必要とする範囲内

したがって、メソッドxを呼び出すときに、関数リテラルのパラメーターを明示的に暗黙的にすることができます。

x{
implicit x => c // the compiler will set the parameter of c like this c(x)
}

これはPlay-Frameworkのアクションメソッドで使用されています

in view folder of app the template is declared like
@()(implicit requestHreader:RequestHeader)

in controller action is like

def index = Action{
implicit request =>

Ok(views.html.formpage())  

}

明示的に暗黙的に要求パラメーターについて言及しない場合は、次のように記述されている必要があります。

def index = Action{
request =>

Ok(views.html.formpage()(request))  

}

4

また、上記の場合only one、型がである暗黙の関数が存在する必要がありdouble => Intます。そうしないと、コンパイラが混乱し、適切にコンパイルされません。

//this won't compile

implicit def doubleToInt(d: Double) = d.toInt
implicit def doubleToIntSecond(d: Double) = d.toInt
val x: Int = 42.0

0

Scalaでの暗黙の非常に基本的な例。

暗黙的なパラメーター

val value = 10
implicit val multiplier = 3
def multiply(implicit by: Int) = value * by
val result = multiply // implicit parameter wiil be passed here
println(result) // It will print 30 as a result

注:ここmultiplierに暗黙的に関数に渡されますmultiply。関数呼び出しへの欠落しているパラメーターは、現在のスコープ内のタイプによって検索されます。つまり、スコープ内にタイプIntの暗黙的な変数がない場合、コードはコンパイルされません。

暗黙的な変換

implicit def convert(a: Double): Int = a.toInt
val res = multiply(2.0) // Type conversions with implicit functions
println(res)  // It will print 20 as a result

注:multiply double値を渡して関数を呼び出すと、コンパイラーは、現在のスコープで変換暗黙関数を見つけようとします。これはIntDouble(関数multiply受け入れIntパラメーターとして)に変換されます。暗黙的なconvert関数がない場合、コンパイラーはコードをコンパイルしません。


0

私はあなたとまったく同じ質問をしました。私がそれを理解し始めた方法をいくつかの本当に簡単な例で共有する必要があると思います(一般的な使用例のみをカバーしていることに注意してください)。

Scalaには、を使用した2つの一般的な使用例がありますimplicit

  • 変数で使用する
  • 関数で使用する

例は次のとおりです

変数で使用する。ご覧のとおりimplicit、最後のパラメーターリストでキーワードが使用されている場合、最も近い変数が使用されます。

// Here I define a class and initiated an instance of this class
case class Person(val name: String)
val charles: Person = Person("Charles")

// Here I define a function
def greeting(words: String)(implicit person: Person) = person match {
  case Person(name: String) if name != "" => s"$name, $words"
    case _ => "$words"
}

greeting("Good morning") // Charles, Good moring

val charles: Person = Person("")
greeting("Good morning") // Good moring

関数で使用する。ご覧のとおりimplicit、関数でを使用すると、最も近い型変換メソッドが使用されます。

val num = 10 // num: Int (of course)

// Here I define a implicit function
implicit def intToString(num: Int) = s"$num -- I am a String now!"

val num = 10 // num: Int (of course). Nothing happens yet.. Compiler believes you want 10 to be an Int

// Util...
val num: String = 10 // Compiler trust you first, and it thinks you have `implicitly` told it that you had a way to covert the type from Int to String, which the function `intToString` can do!
// So num is now actually "10 -- I am a String now!"
// console will print this -> val num: String = 10 -- I am a String now!

これがお役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.