1. ホーム
  2. scala

Scalaの暗黙のパラメーターの良い例?[クローズド]

2023-10-12 07:17:10

質問

これまでのところ、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 を使わないでメッセージを送るという柔軟な使い方を思いついたら、その反対例を投稿してください。言語設計における純粋性というものに興味があるのです;-)。

どのように解決するのですか?

ある意味、そうです、インプリシットはグローバルな状態を表します。しかし、それらは変更可能ではなく、これがグローバル変数の真の問題点です。グローバル定数について文句を言う人は見かけませんよね?実際、コーディング標準では、通常、コード内の定数を定数または列挙型に変換するように定めています。

また、インプリシットは ではなく これはグローバルでよくある問題です。これらは明示的に型に結びつけられ、したがって、それらの型のパッケージ階層に結びつけられます。

では、グローバルは不変で、宣言サイトで初期化され、名前空間上に置かれます。それでもまだグローバルのように見えますか?まだ問題があるように見えますか?

しかし、そこで止まってはいけません。インプリシット であり、型と同じようにグローバルなものです。型がグローバルであるという事実は、あなたを悩ませますか?

ユースケースとしては、たくさんあるのですが、その歴史から簡単におさらいしておきましょう。元々、Scalaにはimplicitがありませんでした。Scalaにあったのはビュータイプで、他の多くの言語が持っていた機能です。これは他の多くの言語が持っている機能です。 T <% Ordered[T] のような書き方をすると,その型は T は型として見ることができます。 Ordered[T] . ビュー型は、型パラメータ(ジェネリクス)で自動キャストを利用できるようにするための方法です。

Scalaはその後 一般化 を一般化しました.自動キャストはもはや存在せず,代わりに 暗黙の変換 があります -- これは単に 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 . その後、実際に使用されるようになりました。 それら のための構文解析はその後に行われました。

そのうちのひとつが コンテキスト・バウンド を実装するために使用される 型クラスパターン (を実装するために使用されます(組み込みの機能ではなく、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

おそらくもう使っていることでしょう -- 普通は気づかないような、よくある使用例がひとつあります。それはこれです。

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つは主にコレクションで使用され、以下のようなメソッドがあります。 max , summap . コンテキストバウンズを多用するライブラリに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!
                        ^

この機能を多用しているライブラリの一つがShapelessである。

Akka ライブラリの例は、これら 4 つのカテゴリのどれにも当てはまらないと思いますが、これが汎用的な機能の要点です:言語設計者が定めた方法ではなく、人々があらゆる方法で使用することができます。

もしあなたが(例えばPythonのように)規定されるのが好きなら、Scalaはあなたには向かないでしょう。