web-dev-qa-db-ja.com

Scalaの暗黙的なパラメーターの良い例?

これまでのところ、Scalaの暗黙的なパラメータは、私にはよく見えません。グローバル変数に近すぎます。ただし、Scala私の意見では疑っています:-)。

質問:暗黙的なパラメーターが実際に機能する場合に、実際の(または近い)良い例を示すことができます。 IOW:showPromptよりも深刻なもの。このような言語設計を正当化するもの.

または逆に、暗黙的でなくてもかまわない信頼性の高い言語設計(架空の場合もあります)を示してください。コードがより明確であり、推測がないため、暗黙のメカニズムよりも優れたメカニズムはないと思います。

、暗黙の関数(変換)ではなく、パラメーターについて尋ねていることに注意してください!

更新情報

グローバル変数

すべての素晴らしい答えをありがとう。 「グローバル変数」の異議を明確にするかもしれません。そのような機能を考慮してください

max(x : Int,y : Int) : Int

あなたはそれを呼ぶ

max(5,6);

次のようにできます(!):

max(x:5,y:6);

しかし、私の目にはimplicitsは次のように機能します。

x = 5;
y = 6;
max()

それはそのようなコンストラクト(PHPのような)とあまり違いません

max() : Int
{
  global x : Int;
  global y : Int;
  ...
}

デレクの答え

これはすばらしい例ですが、implicitを使用せずにメッセージを送信する柔軟な使用法と考えることができる場合は、反例を投稿してください。私は言語設計の純粋さに本当に興味があります;-)。

73
greenoldman

ある意味では、暗黙的はグローバルな状態を表します。しかし、それらは可変ではありません。これはグローバル変数の真の問題です。グローバル定数について不満を言う人はいないでしょうか?実際、コーディング標準では、通常、コード内の定数を定数または列挙型に変換することが規定されていますが、これらは通常グローバルです。

また、フラットな名前空間では暗黙的にnotであることに注意してください。これはグローバルの一般的な問題でもあります。これらは明示的に型に関連付けられているため、それらの型のパッケージ階層に関連付けられています。

したがって、グローバルを取得し、宣言サイトで不変にして初期化し、名前空間に配置します。彼らはまだグローバルのように見えますか?彼らはまだ問題があるように見えますか?

しかし、そこで止まらないようにしましょう。暗黙的なareは型に結び付けられ、型と同じくらい「グローバル」です。型がグローバルであるという事実に悩まされていますか?

ユースケースに関しては、多数ありますが、それらの履歴に基づいて簡単なレビューを行うことができます。もともと、afaik、Scalaには暗黙的なものはありませんでした。 Scalaが持っていたのは、他の多くの言語が備えていたビュータイプでした。 T <% Ordered[T]のようなものを書くときはいつでも、それはT型をOrdered[T]型として見ることができることを今でも見ることができます。ビュー型は、型パラメーター(ジェネリック)で自動キャストを使用可能にする方法です。

その後、Scalaはgeneralized暗黙の機能を備えています。自動キャストはもはや存在せず、代わりに暗黙の変換-があります。これはFunction1値であるため、パラメーターとして渡すことができます。それ以降、T <% Ordered[T]は、暗黙的な変換の値がパラメーターとして渡されることを意味していました。キャストは自動であるため、関数の呼び出し元はパラメーターを明示的に渡す必要はありません-そのため、これらのパラメーターは暗黙的なパラメーターになりました。

暗黙的な変換と暗黙的なパラメータという2つの概念があり、それらは非常に近いものの、完全には重複しないことに注意してください。

とにかく、暗黙的に変換が暗黙的に渡されるため、ビュー型は構文糖衣になりました。これらは次のように書き換えられます。

def max[T <% Ordered[T]](a: T, b: T): T = if (a < b) b else a
def max[T](a: T, b: T)(implicit $ev1: Function1[T, Ordered[T]]): T = if ($ev1(a) < b) b else a

暗黙的なパラメーターは、単にそのパターンの一般化であり、単にFunction1の代わりにany種類の暗黙的なパラメーターを渡すことができます。その後、それらの実際の使用が続き、those使用の構文糖衣が後者になりました。

それらの1つはContext Boundsです。これはtype class patternを実装するために使用されます(組み込み機能ではなく、同様の機能を提供する言語を使用するためのパターンですHaskellの型クラスの機能)。コンテキストバインドは、クラスに固有の機能を実装するアダプターを提供するために使用されますが、クラスによって宣言されません。継承の欠点とインターフェースの利点を提供します。例えば:

def max[T](a: T, b: T)(implicit $ev1: Ordering[T]): T = if ($ev1.lt(a, b)) b else a
// latter followed by the syntactic sugar
def max[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a

あなたはおそらくすでにそれを使用している-人々が通常気づかない1つの一般的なユースケースがあります。これです:

new Array[Int](size)

そのような配列の初期化を可能にするために、クラスマニフェストのコンテキストバインドを使用します。この例でそれを見ることができます:

def f[T](size: Int) = new Array[T](size) // won't compile!

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

def f[T: ClassManifest](size: Int) = new Array[T](size)

標準ライブラリで最も使用されるコンテキスト境界は次のとおりです。

Manifest      // Provides reflection on a type
ClassManifest // Provides reflection on a type after erasure
Ordering      // Total ordering of elements
Numeric       // Basic arithmetic of elements
CanBuildFrom  // Collection creation

後者の3つは、maxsum、およびmapなどのメソッドで、コレクションで主に使用されます。コンテキスト境界を広範囲に使用するライブラリの1つがScalazです。

別の一般的な使用法は、共通のパラメーターを共有する必要がある操作の定型を減らすことです。たとえば、トランザクション:

def withTransaction(f: Transaction => Unit) = {
  val txn = new Transaction

  try { f(txn); txn.commit() }
  catch { case ex => txn.rollback(); throw ex }
}

withTransaction { txn =>
  op1(data)(txn)
  op2(data)(txn)
  op3(data)(txn)
}

これは次のように簡略化されます:

withTransaction { implicit txn =>
  op1(data)
  op2(data)
  op3(data)
}

このパターンはトランザクショナルメモリで使用されますが、Scala I/Oライブラリもそれを使用すると思います(しかし、確信はありません)。

私が考えることができる3番目の一般的な使用法は、渡される型についての証明を作成することです。これにより、実行時に例外が発生する可能性のあるものをコンパイル時に検出できます。たとえば、Optionの次の定義を参照してください。

def flatten[B](implicit ev: A <:< Option[B]): Option[B]

これにより、これが可能になります。

scala> Option(Option(2)).flatten // compiles
res0: Option[Int] = Some(2)

scala> Option(2).flatten // does not compile!
<console>:8: error: Cannot prove that Int <:< Option[B].
              Option(2).flatten // does not compile!
                        ^

その機能を広範囲に使用するライブラリの1つがShapelessです。

Akkaライブラリの例はこれらの4つのカテゴリのいずれにも当てはまらないと思いますが、それが汎用機能のポイントです。人々は言語設計者によって規定された方法ではなく、あらゆる方法でそれを使用できます。

もしあなたが(例えば、Pythonがそうするように)処方されるのが好きなら、Scalaはあなたのためではありません。

97

承知しました。アッカは、そのアクターに関して素晴らしい例があります。アクターのreceiveメソッド内にいるとき、別のアクターにメッセージを送信したい場合があります。これを行うと、Akkaは次のように、メッセージのsenderとして現在のアクターを(デフォルトで)バンドルします。

trait ScalaActorRef { this: ActorRef =>
  ...

  def !(message: Any)(implicit sender: ActorRef = null): Unit

  ...
}

senderは暗黙的です。アクターには、次のような定義があります。

trait Actor {
  ...

  implicit val self = context.self

  ...
}

これにより、独自のコードのスコープ内で暗黙的な値が作成され、次のような簡単なことができます。

someOtherActor ! SomeMessage

必要に応じて、これも実行できます。

someOtherActor.!(SomeMessage)(self)

または

someOtherActor.!(SomeMessage)(null)

または

someOtherActor.!(SomeMessage)(anotherActorAltogether)

しかし、通常はそうではありません。アクタートレイトの暗黙的な値定義によって可能になった自然な使用法を維持するだけです。他にも約100万の例があります。コレクションクラスは巨大です。自明ではないScalaライブラリをさまようと、トラックが見つかります。

23
Derek Wyatt

1つの例は、Traversable[A]の比較操作です。例えば。 maxまたはsort

def max[B >: A](implicit cmp: Ordering[B]) : A

これらは、Aに操作<がある場合にのみ、賢明に定義できます。したがって、暗黙的にせずに、この関数を使用するたびにコンテキストOrdering[B]を提供する必要があります。 (またはmax内の型の静的チェックを放棄し、ランタイムキャストエラーのリスクがあります。)

ただし、暗黙的な比較type classがスコープ内にある場合。いくつかのOrdering[Int]、すぐに使用するか、暗黙的なパラメーターに他の値を指定して比較方法を変更するだけです。

もちろん、暗黙的は隠されている可能性があり、したがって、スコープ内にある実際の暗黙的が十分に明確でない状況が存在する可能性があります。 maxまたはsortを単純に使用する場合、traitに固定の順序Intを設定し、この特性が使用可能かどうかを確認する構文を使用すれば十分です。しかし、これはアドオンの特性がなく、すべてのコードが最初に定義された特性を使用する必要があることを意味します。

追加:
グローバル変数比較に対する応答。

私はあなたが正しいと思います

implicit val num = 2
implicit val item = "Orange"
def shopping(implicit num: Int, item: String) = {
  "I’m buying "+num+" "+item+(if(num==1) "." else "s.")
}

scala> shopping
res: Java.lang.String = I’m buying 2 Oranges.

腐敗した悪のグローバル変数の匂いがするかもしれません。ただし、重要な点は、スコープ内に暗黙的な変数per typeが1つしかないことです。 2つのIntsを使用した例は機能しません。

また、これは、実際には、型に対して必ずしも一意ではないが別個のプライマリインスタンスが存在しない場合にのみ、暗黙的な変数が使用されることを意味します。アクターのself参照は、そのようなことの良い例です。型クラスの例は別の例です。任意の型に対して数十の代数比較がありますが、特別なものがあります。 (別のレベルでは、コード自体の実際の行番号も、非常に特徴的なタイプを使用している限り、適切な暗黙変数になります。)

通常、日常のタイプにはimplicitsを使用しません。また、特殊なタイプ(Ordering[Int]など)を使用すると、それらをシャドウイングするリスクはあまりありません。

9
Debilski

私の経験に基づいて、暗黙的なパラメーターまたは暗黙的な変換の使用のための実際の良い例はありません。

暗黙的(パラメータまたは型を明示的に記述する必要がない)を使用することの小さな利点は、それらが作成する問題と比較して冗長です。

私は15年間開発者であり、過去1.5年間にわたってscalaを扱ってきました。

開発者が暗黙の使用を認識していないこと、および特定の関数が実際に指定したものとは異なるタイプを返すことを知らないことによって引き起こされたバグを何度も目にしました。暗黙的な変換のため。

また、暗黙のコードが気に入らない場合は使用しないでくださいという声明も聞きました。これは現実の世界では実用的ではありません。外部ライブラリが何度も使用されており、それらの多くが暗黙的を使用しているため、コードが暗黙的を使用していることに気づかないかもしれません。次のいずれかを持つコードを作成できます。

import org.some.common.library.{TypeA, TypeB}

または:

import org.some.common.library._

両方のコードがコンパイルおよび実行されます。ただし、2番目のバージョンでは暗黙的な変換によりコードの動作が異なるため、常に同じ結果が得られるとは限りません。

この変換によって影響を受けるいくつかの値が元々使用されなかった場合、これによって引き起こされる「バグ」は、コードが記述された後、非常に長い時間発生する可能性があります。

バグに遭遇すると、原因を見つけるのは簡単な作業ではありません。いくつかの深い調査を行う必要があります。

scalaの専門家のように感じても、バグを発見し、importステートメントを変更して修正した場合、実際には多くの貴重な時間を無駄にしています。

私が暗黙的に一般的に反対する追加の理由は次のとおりです。

  • 彼らはコードを理解するのを難しくします(より少ないコードがありますが、あなたは彼が何をしているのかわかりません)
  • コンパイル時間。 scala暗黙のコードが使用されると、コードのコンパイルが非常に遅くなります。
  • 実際には、言語を静的に型付けされた言語から動的に型付けされた言語に変更します。非常に厳しいコーディングガイドラインに従うと、そのような状況を回避できることは事実ですが、現実には常にそうとは限りません。 IDE 'remove used imports'を使用しても、コードはコンパイルおよび実行されますが、 'unused' importsを削除する前とは異なります。

scala暗黙的にせずにコンパイルするオプションはありません(ある場合は修正してください)、オプションがあった場合、一般的なコミュニティはありませんscalaコンパイルしていたでしょう。

上記のすべての理由から、暗黙的はscala言語が使用している最悪のプラクティスの1つであると思います。

Scalaには多くの優れた機能があり、それほど多くはありません。

新しいプロジェクトのために言語を選択するとき、暗黙はscalaに対する理由の1つであり、それを支持するものではありません。私の考えでは。

5
noam

簡単です、覚えておいてください:

  • 暗黙的に渡される変数も宣言する
  • 個別の()で非暗黙的なパラメータの後にすべての暗黙的なパラメータを宣言する

例えば.

def myFunction(): Int = {
  implicit val y: Int = 33
  implicit val z: Double = 3.3

  functionWithImplicit("foo") // calls functionWithImplicit("foo")(y, z)
}

def functionWithImplicit(foo: String)(implicit x: Int, d: Double) = // blar blar
4
samthebest

暗黙的なパラメーターのもう1つの一般的な使用方法は、メソッドの戻り値の型を、渡されるパラメーターの一部の型に依存させることです。 Jensが言及した良い例は、コレクションフレームワーク、およびmapなどのメソッドです。通常、その完全な署名は次のとおりです。

def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[GenSeq[A], B, That]): That

戻り型Thatは、コンパイラが検出できる最適なCanBuildFromによって決定されることに注意してください。

この別の例については、 その答え を参照してください。そこでは、メソッドの戻り型Arithmetic.applyは、特定の暗黙的なパラメータータイプ(BiConverter)に従って決定されます。

暗黙的なパラメーターは、コレクションAPIで頻繁に使用されます。多くの関数は暗黙的なCanBuildFromを取得します。これにより、「最適な」結果コレクションの実装を確実に取得できます。

暗黙的でないと、そのようなものを常に渡すことになり、通常の使用法が面倒になります。または、パフォーマンス/パワーが低下することを意味するため、迷惑な特殊なコレクションを使用します。

3
Jens Schauder

私はこの投稿に少し遅れてコメントしていますが、私は学習を始めましたscala=最近。ダニエルと他の人は暗黙のキーワードについてニースの背景を与えました。視点。

Scalaは、Apache Sparkコードの記述に使用する場合に最適です。Sparkには、sparkコンテキストがあり、ほとんどの場合、構成キーをフェッチする構成クラスがあります/構成ファイルからの値。

さて、抽象クラスがあり、設定とspark contextのオブジェクトを次のように宣言すると:-

abstract class myImplicitClass {

implicit val config = new myConfigClass()

val conf = new SparkConf().setMaster().setAppName()
implicit val sc = new SparkContext(conf)

def overrideThisMethod(implicit sc: SparkContext, config: Config) : Unit
}

class MyClass extends myImplicitClass {

override def overrideThisMethod(implicit sc: SparkContext, config: Config){

/*I can provide here n number of methods where I can pass the sc and config 
objects, what are implicit*/
def firstFn(firstParam: Int) (implicit sc: SparkContext, config: Config){ 
    /*I can use "sc" and "config" as I wish: making rdd or getting data from cassandra, for e.g.*/
    val myRdd = sc.parallelize(List("abc","123"))
}
def secondFn(firstParam: Int) (implicit sc: SparkContext, config: Config){
 /*following are the ways we can use "sc" and "config" */

        val keyspace = config.getString("keyspace")
        val tableName = config.getString("table")
        val hostName = config.getString("Host")
        val userName = config.getString("username")
        val pswd = config.getString("password")

    implicit val cassandraConnectorObj = CassandraConnector(....)
    val cassandraRdd = sc.cassandraTable(keyspace, tableName)
}

}
}

上記のコードを見るとわかるように、抽象クラスに2つの暗黙的なオブジェクトがあり、それら2つの暗黙的な変数を関数/メソッド/定義の暗黙的なパラメーターとして渡しました。これは、暗黙的な変数の使用に関して説明できる最良のユースケースだと思います。

0
anshuman sharma