Scala関数(別のScalaツアーの一部)を読みました。その投稿で彼は述べた:
メソッドと関数は同じではありません
しかし、彼はそれについて何も説明しませんでした。彼は何を言おうとしていたのですか。
Scala関数(別のScalaツアーの一部)を読みました。その投稿で彼は述べた:
メソッドと関数は同じではありません
しかし、彼はそれについて何も説明しませんでした。彼は何を言おうとしていたのですか。
回答:
ジムはこれを彼のブログ記事でほとんどカバーしていますが、参考のためにここにブリーフィングを投稿しています。
まず、Scala仕様の内容を見てみましょう。第3章(型)では、関数型(3.2.9)とメソッド型(3.3.1)について説明しています。第4章(基本宣言)では、値の宣言と定義(4.1)、変数の宣言と定義(4.2)、関数の宣言と定義(4.6)について説明しています。第6章(式)では、無名関数(6.23)およびメソッド値(6.7)について説明します。不思議なことに、関数値は3.2.9で一度だけ話され、他の場所では話されません。
関数型は(おおよそ)形式の一種である(T1、...、TN)=> U形質の短縮形であり、FunctionN
標準ライブラリです。無名関数とメソッド値には関数型があり、関数型は値、変数、関数の宣言と定義の一部として使用できます。実際、それはメソッド型の一部になることができます。
A 法タイプである非値型。つまり、メソッドタイプには値がなく、オブジェクトもインスタンスもありません。上記のように、メソッド値には実際には関数タイプがあります。メソッド型はdef
宣言です- def
本体以外のすべて。
値の宣言と定義および変数の宣言と定義はval
、型と値のvar
両方を含む宣言であり、それぞれ、関数の型と無名関数またはメソッドの値にすることができます。JVMでは、これら(メソッド値)はJavaが「メソッド」と呼ぶもので実装されることに注意してください。
関数宣言は あるdef
含め宣言、タイプおよびボディ。タイプ部分はメソッドタイプであり、本体は式またはブロックです。これは、Javaが「メソッド」と呼ぶものを使用して、JVMにも実装されます。
最後に、無名関数は関数タイプのインスタンス(つまり、特性のインスタンスFunctionN
)であり、メソッド値も同じです。違いは、メソッド値は、アンダースコアを後置することによって(m _
「関数宣言」(def
)に対応するメソッド値ですm
)、またはeta-expansionと呼ばれるプロセスによってメソッドから作成されることです。機能する。
それが仕様書に書かれていることなので、私はこれを前置きしておきます。その用語は使用しません!これは、プログラムの一部であるいわゆる「関数宣言」(第4章-基本的な宣言)と式である「無名関数」と、「関数型」との間の混乱を引き起こします。よくタイプ-特性。
以下の用語、および経験豊富なScalaプログラマーが使用する用語は、仕様の用語から1つの変更を加えたものです。関数の宣言と言う代わりに、メソッドと言います。またはメソッド宣言。さらに、値の宣言と変数の宣言も実用的な方法であることに注意してください。
したがって、上記の用語の変更を踏まえて、ここで区別の実際的な説明を示します。
関数は、のいずれかを含むオブジェクトであるFunctionX
ような形質、Function0
、Function1
、Function2
、などこれを含む可能性がありPartialFunction
、実際に延びる、同様にFunction1
。
次のいずれかの特性の型シグネチャを見てみましょう。
trait Function2[-T1, -T2, +R] extends AnyRef
このトレイトには1つの抽象メソッドがあります(具体的なメソッドもいくつかあります)。
def apply(v1: T1, v2: T2): R
そして、それはそれについて知る必要があることを私たちにすべて伝えます。機能は有しているapply
受信方法NのタイプのパラメータT1、T2、...、TN、およびリターンタイプのものをR
。受け取るパラメーターは反変で、結果は共変です。
その差異は、a Function1[Seq[T], String]
がのサブタイプであることを意味しFunction1[List[T], AnyRef]
ます。サブタイプであることは、サブタイプの代わりに使用できることを意味します。電話をかけf(List(1, 2, 3))
て返事を期待するAnyRef
場合、上記の2つのタイプのいずれかが機能することが簡単にわかります。
では、メソッドと関数の類似点は何でしょうか。まあ、f
が関数でありm
、スコープに対してローカルなメソッドである場合、両方を次のように呼び出すことができます。
val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))
最初の呼び出しは単なる構文上の砂糖なので、これらの呼び出しは実際には異なります。Scalaはそれを次のように拡張します。
val o1 = f.apply(List(1, 2, 3))
もちろん、これはobjectのメソッド呼び出しf
です。関数には、他にも構文上の利点があります。関数リテラル(実際には2つ)と(T1, T2) => R
型シグネチャです。例えば:
val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
case i: Int => "Int"
case d: Double => "Double"
case o => "Other"
}
メソッドと関数のもう1つの類似点は、前者を簡単に後者に変換できることです。
val f = m _
タイプが(Scala 2.7)であると仮定すると、Scalaはそれを拡張します。m
(List[Int])AnyRef
val f = new AnyRef with Function1[List[Int], AnyRef] {
def apply(x$1: List[Int]) = this.m(x$1)
}
Scala 2.8では、実際にはAbstractFunction1
クラスを使用してクラスのサイズを小さくしています。
関数からメソッドへと逆に変換できないことに注意してください。
ただし、メソッドには1つの大きな利点があります(2つありますが、少し高速です)。型パラメーターを受け取ることができます。たとえば、f
上記では必ずList
受け取るタイプを指定できますが(List[Int]
例では)、m
パラメータ化できます。
def m[T](l: List[T]): String = l mkString ""
これはほとんどすべてをカバーしていると思いますが、残っている可能性のある質問への回答でこれを補完させていただきます。
val f = m
コンパイラによるの展開を引用すると、メソッドval f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
のthis
内部apply
はAnyRef
オブジェクトを参照するのではなく、メソッドval f = m _
が評価されるオブジェクト(外部 this
、つまり)this
は、クロージャーによってキャプチャされる値の中にあるため(たとえばreturn
、以下で指摘されているように)。
メソッドと関数の実用的な大きな違いの1つは、そのreturn
意味です。 return
メソッドから戻るだけです。例えば:
scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
val f = () => { return "test" }
^
メソッドで定義された関数から戻ると、ローカル以外の戻り値が返されます。
scala> def f: String = {
| val g = () => { return "test" }
| g()
| "not this"
| }
f: String
scala> f
res4: String = test
ローカルメソッドからの戻りは、そのメソッドからのみ戻ります。
scala> def f2: String = {
| def g(): String = { return "test" }
| g()
| "is this"
| }
f2: String
scala> f2
res5: String = is this
for (a <- List(1, 2, 3)) { return ... }
どうですか?それは閉鎖に砂糖を取り除かれます。
return
関数から値を返し、何らかの形escape
またはbreak
またはcontinue
メソッドから復帰します。
function関数は、結果を生成する引数のリストを使用して呼び出すことができます。関数には、パラメーター・リスト、本体、および結果タイプがあります。クラス、トレイト、またはシングルトンオブジェクトのメンバーである関数はメソッドと呼ばれます。他の関数の内部で定義された関数は、ローカル関数と呼ばれます。結果タイプがUnitの関数は、プロシージャと呼ばれます。ソースコードの無名関数は、関数リテラルと呼ばれます。実行時に、関数リテラルは関数値と呼ばれるオブジェクトにインスタンス化されます。
リストがあるとしましょう
scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
メソッドを定義する
scala> def m1(i:Int)=i+2
m1: (i: Int)Int
関数を定義する
scala> (i:Int)=>i+2
res0: Int => Int = <function1>
scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
引数を受け入れるメソッド
scala> m1(2)
res3: Int = 4
valを使用した関数の定義
scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>
関数への引数はオプションです
scala> p(2)
res4: Int = 4
scala> p
res5: Int => Int = <function1>
メソッドへの引数は必須です
scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function
他の違いを渡す方法を説明する次のチュートリアルを確認してください。例として、メソッドと関数を使用したdiffの他の例、関数を変数として使用、関数を返す関数の作成
関数はパラメーターのデフォルトをサポートしていません。メソッドにはあります。メソッドから関数に変換すると、パラメーターのデフォルトが失われます。(スカラ2.8.1)
ここに私の記事のほとんどが取られている素晴らしい記事があります。私の理解に関する関数とメソッドの短い比較。それが役に立てば幸い:
機能:基本的にオブジェクトです。より正確には、関数は、applyメソッドを持つオブジェクトです。したがって、オーバーヘッドがあるため、メソッドよりも少し遅くなります。呼び出されるオブジェクトから独立しているという意味では、静的メソッドに似ています。関数の簡単な例は、次のようなものです。
val f1 = (x: Int) => x + x
f1(2) // 4
上記の行は、object1 = object2のように、あるオブジェクトを別のオブジェクトに割り当てることを除いて何もありません。実際、この例のobject2は無名関数であり、左側がそのためにオブジェクトのタイプを取得します。したがって、f1はオブジェクト(関数)になります。匿名関数は、実際にはFunction1 [Int、Int]のインスタンスであり、Int型の1つのパラメーターとInt型の戻り値を持つ関数を意味します。引数なしでf1を呼び出すと、無名関数の署名が得られます(Int => Int =)
メソッド:これらはオブジェクトではなく、クラスのインスタンス、つまりオブジェクトに割り当てられます。Javaのメソッドまたはc ++のメンバー関数とまったく同じ(Raffi Khatchadourianがコメントで指摘したとおり)この質問)などとまったく同じです。メソッドの簡単な例は次のようになります。
def m1(x: Int) = x + x
m1(2) // 4
上記の行は単純な値の割り当てではなく、メソッドの定義です。2行目のように値2でこのメソッドを呼び出すと、xが2に置き換えられ、結果が計算され、出力として4が得られます。これはメソッドであり、入力値が必要なため、単にm1と書くだけの場合はエラーになります。_を使用すると、次のような関数にメソッドを割り当てることができます。
val f2 = m1 _ // Int => Int = <function1>
ここに違いを説明するロブ・ノリスによる素晴らしい投稿があります、これはTLです; DR
Scalaのメソッドは値ではありませんが、関数は値です。η-expansionを介してメソッドに委譲する関数を構築できます(末尾のアンダースコアがトリガーされます)。
次の定義で:
この方法は、と定義されたものですDEFと値は、あなたがに割り当てることができるものであるヴァル
一言で言えば(ブログから抜粋):
メソッドを定義すると、それをに割り当てることができないことがわかりますval
。
scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int
scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
val f = add1
のタイプにも注意してくださいadd1
。これは正常に見えません。型の変数を宣言することはできません(n: Int)Int
。メソッドは値ではありません。
ただし、η-拡張後置演算子(ηは「eta」と発音します)を追加することで、メソッドを関数値に変換できます。のタイプに注意してくださいf
。
scala> val f = add1 _
f: Int => Int = <function1>
scala> f(3)
res0: Int = 4
の効果は_
、以下と同等の処理を実行するFunction1
ことです。メソッドに委譲するインスタンスを作成します。
scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>
scala> g(3)
res18: Int = 4
Scala 2.13では、関数とは異なり、メソッドは取る/返すことができます
ただし、これらの制限は多態性関数タイプ#4672によってdotty(Scala 3)で解除されます。たとえば、dottyバージョン0.23.0-RC1は次の構文を有効にします
型パラメーター
def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
暗黙的なパラメーター(コンテキストパラメーター)
def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero
依存型
class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet
その他の例については、tests / run / polymorphic-functions.scalaを参照してください
実際には、Scalaプログラマは、関数とメソッドを適切に使用するために、次の3つのルールを知っていれば十分です。
def
関数リテラル=>
は関数です。これは、 『Scalaでのプログラミング』第4版の143ページ、第8章で定義されています。someNumber.foreach(println)
『Scalaでのプログラミング』の4つのエディションの後、すべてのエディションが明確な説明を提供していないため、関数と関数の値という2つの重要な概念を区別することは依然として問題です。言語仕様が複雑すぎます。上記のルールはシンプルで正確であることがわかりました。