web-dev-qa-db-ja.com

Scalaで暗黙のうちに理解する

私はScalaのplayframeworkチュートリアルをやり遂げていました、そして私が戸惑っていたこのコードの断片に出会いました:

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はいつ使用すべきですか。

284
Clive

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

暗黙のパラメータ

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

例:

  // 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"

暗黙の変換

コンパイラがコンテキストに対して間違った型の式を見つけると、型チェックを許可する型の暗黙のFunctionname__値を探します。そのため、Aname__が必要であり、Bname__が見つかった場合は、スコープ内でB => A型の暗黙的な値を探します(存在する場合は、Bname__およびAname__コンパニオンオブジェクトなど、他の場所もチェックします)。 defname__sはFunctionname__オブジェクトに "eta-expansion"できるので、implicit def xyz(arg: B): Aも同様にできます。

したがって、implicitname__が見つかったがDoublename__が必要な場合は、Intname__とマークされたものがコンパイラーによって自動的に挿入されるという点が、メソッドの違いです。

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 docs もご覧ください)。あなたが使っている

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

Actionname__オブジェクト(これは同名の特性の仲間です)。

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

request => ...

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

359
Luigi Plinge

警告:は慎重に皮肉を含みます! YMMV ...

Luigi's answer 完全で正しいです。これは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")
}
34
Daniel Dinnyes

requestパラメーターをimplicitとしてマークする理由とタイミング

アクションの本体で使用するメソッドの中には、暗黙のパラメータリストを持つものがあります。たとえば、Form.scalaはメソッドを定義します。

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

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

あなたは明示的に利用可能としてそれを暗黙的使用のために印を付けます。

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

myForm.bindFromRequest()

それを見ますか?それはありませんが、それはisあります!

必要なすべての場所に手動でスロットを挿入する必要はありません(ただし、implicitとマークされているかどうかにかかわらず、can明示的に渡すことができます)。

myForm.bindFromRequest()(request)

それを暗黙のうちにマークしないで、あなたはしなければならないでしょう。暗黙のうちにそれをマーキングする必要はありません。

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

6
Peter Perháč

また、上記の場合、型が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
4
rileyss

scalaで暗黙的に機能する

コンバーター

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

暗黙の使用には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受信者に検索するためです。見つかりません。次に、引数としてMahadiオブジェクトを取り、Johnnyオブジェクトを返す暗黙キーワードを持つスコープ内のメソッドを探します。しかし、ここにはありません。 errorを作成します。しかし、次は大丈夫です。

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
    

次に、パラメーターに暗黙的なキーワードがある場合、スコープ同じタイプの値を持つvalを探します。取得しない場合、エラーが発生します。

def x(implicit a:Int)= a

x // error happening here

この問題を解決するために、コンパイラはimplicit valを探します-type of Intパラメータaにはimplicitキーワードがあるため=。

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の本体のスコープ内にあるため、暗黙のローカル変数パラメータはローカル変数ですaこれは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
}

c in x {x => c}引数または暗黙のval スコープ内で明示的に値を渡す必要があるため、エラーが発生します。

したがって、method xを呼び出すときに、関数リテラルのparameterを明示的に作成できますimplicit

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

これは、Play-Frameworkのaction methodで使用されています

def index = Action{
implicit request =>

Ok(views.html.formpage)

}
0
MHJ