Scalaにおけるメソッドと関数の違い


254

Scala関数別のScalaツアーの一部)を読みました。その投稿で彼は述べた:

メソッドと関数は同じではありません

しかし、彼はそれについて何も説明しませんでした。彼は何を言おうとしていたのですか。



3
私はあなたから何かを得ることができると思う方法と機能の違いは何ですか
jinglining

良い答えを伴うフォローアップの質問:Scalaの関数とメソッド
Josiah Yoder

回答:


238

ジムはこれを彼のブログ記事でほとんどカバーしていますが、参考のためにここにブリーフィングを投稿しています。

まず、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ような形質、Function0Function1Function2、などこれを含む可能性がありPartialFunction、実際に延びる、同様にFunction1

次のいずれかの特性の型シグネチャを見てみましょう。

trait Function2[-T1, -T2, +R] extends AnyRef

このトレイトには1つの抽象メソッドがあります(具体的なメソッドもいくつかあります)。

def apply(v1: T1, v2: T2): R

そして、それはそれについて知る必要があることを私たちにすべて伝えます。機能は有しているapply受信方法NのタイプのパラメータT1T2、...、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 ""

これはほとんどすべてをカバーしていると思いますが、残っている可能性のある質問への回答でこれを補完させていただきます。


26
この説明は非常に明確です。よくやった。あいにく、Odersky / Venners / Spoonの本とScala仕様の両方で、「関数」と「メソッド」という言葉は多少同じ意味で使用されています。(彼らは「関数」と言っているようですが、「メソッド」の方が明確ですが、逆の場合もあります。たとえば、メソッドを関数に変換することをカバーする仕様のセクション6.7は「メソッド値」と呼ばれます。 。)人々が言語を学ぼうとするとき、これらの単語のゆるい使用は多くの混乱を引き起こしたと思います。
Seth Tisue、

4
@Seth私は知っている、私は知っている-PinSは私にScalaを教えた本だった。私は難しい方法をよりよく学びました、すなわち、paulpは私をまっすぐにしました。
ダニエルC.ソブラル2010年

4
素晴らしい説明!追加すべきことは1つあります。val f = mコンパイラによるの展開を引用すると、メソッドval f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }this内部applyAnyRefオブジェクトを参照するのではなく、メソッドval f = m _が評価されるオブジェクト(外部 this、つまり)thisは、クロージャーによってキャプチャされる値の中にあるため(たとえばreturn、以下で指摘されているように)。
Holger Peine 2013年

1
@ DanielC.Sobral、あなたが言及したPinSの本は何ですか?私はScalaの学習にも興味があり、その名前の本を見つけていません
tldr

5
@tldr プログラミング(ScalaでのOdersky らによる)これは一般的な略語です(何らかの理由でPiSがあまり好きではなかったと言われていました!:)
Daniel C. Sobral 2013年

67

メソッドと関数の実用的な大きな違いの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

9
これは、リターンがクロージャーによって捕捉されるためです。
ダニエルC.ソブラル

4
関数から非ローカルスコープに「戻りたい」と思うことは一度もありません。実際、関数がスタックをさらに後戻りしたいと判断できる場合、それは深刻なセキュリティ問題であると私は見ることができます。longjmpのような感じがしますが、誤って間違ってしまうのは簡単なことです。scalacでは関数から復帰できないことに気づきました。それは、この嫌悪感が言語から打たれたということですか
ルートの

2
@root-内部から戻るのはfor (a <- List(1, 2, 3)) { return ... }どうですか?それは閉鎖に砂糖を取り除かれます。
Ben Lings 2013

うーん...まあ、それは合理的なユースケースです。それでも、恐ろしいデバッグ困難な問題につながる可能性がありますが、それはより理にかなった状況になります。
ルートは

1
正直なところ、私は別の構文を使用します。有するreturn関数から値を返し、何らかの形escapeまたはbreakまたはcontinueメソッドから復帰します。
ライアンザリーチ2016年

38

function関数は、結果を生成する引数のリストを使用して呼び出すことができます。関数には、パラメーター・リスト、本体、および結果タイプがあります。クラス、トレイト、またはシングルトンオブジェクトのメンバーである関数はメソッドと呼ばれます。他の関数の内部で定義された関数は、ローカル関数と呼ばれます。結果タイプがUnitの関数は、プロシージャと呼ばれます。ソースコードの無名関数は、関数リテラルと呼ばれます。実行時に、関数リテラルは関数値と呼ばれるオブジェクトにインスタンス化されます。

Scala Second Editionでのプログラミング。マーティンオデルスキー-レックススプーン-ビルベナーズ


1
関数は、defまたはval / varとしてクラスに属することができます。defのみがメソッドです。
Josiah Yoder

29

リストがあるとしましょう

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の他の例、関数を変数として使用、関数を返す関数の作成


13

関数はパラメーターのデフォルトをサポートしていません。メソッドにはあります。メソッドから関数に変換すると、パラメーターのデフォルトが失われます。(スカラ2.8.1)


5
これには理由がありますか?
corazza 14年

7

ここに私の記事のほとんどが取られている素晴らしい記事があります。私の理解に関する関数とメソッドの短い比較。それが役に立てば幸い:

機能:基本的にオブジェクトです。より正確には、関数は、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>

「メソッドを関数に割り当てる」とはどういう意味ですか?これは、メソッドが行ったのと同じように動作するオブジェクトがあることを意味するだけですか?
K. M

@KM:val f2 = m1 _はval f2 = new Function1 [Int、Int] {def m1(x:Int)= x + x}と同等です。
サスケ

3

ここに違いを説明するロブ・ノリスによる素晴らしい投稿があります、これは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

1

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を参照してください


0

実際には、Scalaプログラマは、関数とメソッドを適切に使用するために、次の3つのルールを知っていれば十分です。

  • によって定義されたメソッドとによって定義されたdef関数リテラル=>は関数です。これは、 『Scalaでのプログラミング』第4版の143ページ、第8章で定義されています。
  • 関数値は、任意の値として渡すことができるオブジェクトです。関数リテラルと部分的に適用される関数は関数値です。
  • コードのある時点で関数の値が必要な場合は、部分的に適用された関数の下線を省略できます。例えば:someNumber.foreach(println)

『Scalaでのプログラミング』の4つのエディションの後、すべてのエディションが明確な説明を提供していないため、関数と関数の値という2つの重要な概念を区別することは依然として問題です。言語仕様が複雑すぎます。上記のルールはシンプルで正確であることがわかりました。

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