関数を定義するための「def」と「val」の違いは何ですか


214

違いは何ですか:

def even: Int => Boolean = _ % 2 == 0

そして

val even: Int => Boolean = _ % 2 == 0

どちらものように呼び出すことができますeven(10)


こんにちは、どういうInt => Boolean意味ですか?私は、define構文はdef foo(bar: Baz): Bin = expr
Ziu

@Ziuは、関数 'even'が引数としてIntを受け取り、値の型としてブール値を返すことを意味します。したがって、ブール値「false」に評価される「even(3)」を呼び出すことができます
Denys Lobur

@DenysLoburご返信ありがとうございます!この構文に関する参照はありますか?
Ziu 2018年

@Ziu基本的には、OderskyのCourseraコース-Coursera.org/learn/progfun1から見つけました。完了すると、「タイプ=>タイプ」の意味が理解できます
Denys Lobur

回答:


325

メソッドdef evenは呼び出し時に評価され、毎回新しい関数を作成します(の新しいインスタンスFunction1)。

def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false

val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

defあなたがすべての呼び出しに新しい機能を取得することができます:

val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1049057402
test()
// Int = -1049057402 - same result

def test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -240885810
test()
// Int = -1002157461 - new result

val定義されたときに評価されますdef-呼び出されたとき:

scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing

scala> def even: Int => Boolean = ???
even: Int => Boolean

scala> even
scala.NotImplementedError: an implementation is missing

3番目のオプションがあることに注意してください。 lazy val

初めて呼び出されたときに評価されます。

scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>

scala> even
scala.NotImplementedError: an implementation is missing

ただし、FunctionN毎回同じ結果(この場合はの同じインスタンス)を返します。

lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

lazy val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1068569869
test()
// Int = -1068569869 - same result

パフォーマンス

val 定義時に評価します。

defは呼び出しごとに評価するvalため、複数の呼び出しよりもパフォーマンスが低下する可能性があります。1回の呼び出しで同じパフォーマンスが得られます。また、呼び出しがないため、からオーバーヘッドdefが発生しないため、一部のブランチで使用しなくても定義できます。

を使用するlazy valと、遅延評価が得られます。一部のブランチでそれを使用しない場合でも定義でき、評価は1回またはまったく行われませんlazy val

@SargeBorschが指摘したように、メソッドを定義することができ、これが最速のオプションです。

def even(i: Int): Boolean = i % 2 == 0

ただし、関数の構成または高次の関数(などfilter(even))に関数(メソッドではない)が必要な場合、コンパイラーは関数として使用するたびにメソッドから関数を生成するため、を使用する場合よりもパフォーマンスがわずかに低下する可能性がありますval


性能について比較していただけませんか?even呼び出されるたびに関数を評価することは重要ではありませんか。
Amir Karimi 2013

2
defメソッドを定義するために使用でき、これが最速のオプションです。@ A.Karimi
表示名

2
楽しみのために:2.12にeven eq even
som-snytt 2016

C ++のようなインライン関数の概念はありますか?私はC ++の世界から来ているので、私の無知を許してください。
animageofmine 2017年

2
@animageofmine Scalaコンパイラは、メソッドのインライン化を試みることができます。これには@inline属性があります。ただし、関数呼び出しはapply関数オブジェクトの仮想メソッドの呼び出しであるため、関数をインライン化することはできません。JVMは、状況によってはこのような呼び出しを仮想化およびインライン化する場合がありますが、一般的にはそうではありません。
セニア2017年

24

このことを考慮:

scala> def even: (Int => Boolean) = {
             println("def"); 
             (x => x % 2 == 0)
       }
even: Int => Boolean

scala> val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>

scala> even(1)
def
res9: Boolean = false

scala> even2(1)
res10: Boolean = false

違いがわかりますか?要するに:

def:を呼び出すたびevenに、evenメソッドの本体が再度呼び出されます。しかしeven2ie valを使用すると、関数は宣言時に1回だけ初期化され(したがって、val4行目に出力され、それ以降は表示されません)、アクセスするたびに同じ出力が使用されます。たとえば、これを試してください:

scala> import scala.util.Random
import scala.util.Random

scala> val x = { Random.nextInt }
x: Int = -1307706866

scala> x
res0: Int = -1307706866

scala> x
res1: Int = -1307706866

ときにx初期化され、値が返されたことにより、Random.nextInt最終的な値として設定されていますx。次回xは再度使用すると、常に同じ値が返されます。

遅延初期化することもできますx。つまり、最初に使用されるときは、宣言時ではなく初期化されます。例えば:

scala> lazy val y = { Random.nextInt }
y: Int = <lazy>

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
あなたの説明はあなたが意図していないことを暗示していると思います。とで1 even2回ずつ、2回呼び出してみます。通話ごとに異なる回答が得られます。したがって、後続の呼び出しではは実行されませんが、への異なる呼び出しから同じ結果が得られることはありません。が再度実行されない理由については、別の質問です。12printlneven2println
メルストン

1
それは実際には非常に興味深いです。これは、val、つまりeven2の場合のようで、valはパラメーター化された値に評価されます。ですから、valを指定すると、関数とその値の評価が得られます。printlnは評価値の一部ではありません。これは評価の一部ですが、評価値ではありません。ここでの秘訣は、評価された値が実際にはパラメータ化された値であり、入力に依存することです。スマートなこと
MaatDeamon、2015年

1
@melstonまさに!それが私が理解したことですが、なぜ出力が変更されているときにprintlnが再度実行されないのですか?
2015年

1
Even2によって返される@aurは、実際には関数(even2の定義の最後にある括弧で囲まれた式)です。その関数は、実際にそれを呼び出すたびにeven2に渡すパラメーターで呼び出されます。
メルストン2015

5

これを見てください:

  var x = 2 // using var as I need to change it to 3 later
  val sq = x*x // evaluates right now
  x = 3 // no effect! sq is already evaluated
  println(sq)

驚いたことに、これは9ではなく4を印刷します。val(varも)はすぐに評価され、割り当てられます。
次に、valをdefに変更します。9と表示されます。Defは関数呼び出しです。呼び出されるたびに評価されます。


1

valつまり「sq」はScalaの定義によるもので、修正されています。宣言時に正しく評価され、後で変更することはできません。その他の例では、even2もvalですが、関数シグネチャ、つまり "(Int => Boolean)"で宣言されているため、Int型ではありません。それは関数であり、その値は次の式によって設定されます

   {
         println("val");
         (x => x % 2 == 0)
   }

Scalaのvalプロパティに従って、sqと同じ規則で、even2に別の関数を割り当てることはできません。

eval2 val関数を呼び出すと「val」が何度も出力されないのはなぜですか?

元のコード:

val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }

Scalaでは、上記の式({..}内)の最後のステートメントは実際には左側に戻ります。つまり、even2を "x => x%2 == 0"関数に設定することになります。これは、even2 val型に対して宣言した型(Int => Boolean)と一致するため、コンパイラーは幸せです。Even2は "(x => x%2 == 0)"関数(つまり、println( "val")などの前の他のステートメントではない)のみを指すようになりました。異なるパラメーターでevent2を呼び出すと、実際には "(x => x%2 == 0) "コード、それだけがevent2で保存されるため。

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

これをより明確にするために、以下はコードの異なるバージョンです。

scala> val even2: (Int => Boolean) = {
     |              println("val");
     |              (x => { 
     |               println("inside final fn")
     |               x % 2 == 0
     |             })
     |        }

何が起こるか ?ここで、even2()を呼び出すと、「inside final fn」が何度も何度も出力されます。

scala> even2(3)
inside final fn
res9: Boolean = false

scala> even2(2)
inside final fn
res10: Boolean = true

scala> 

1

などの定義を実行するdef x = eと、式eは評価されません。代わりに、xが呼び出されるたびにeが評価されます。

あるいは、Scalaは値の定義を提供します val x = e。これは、定義の評価の一部として右側を評価します。次にxを続けて使用すると、事前に計算されたeの値ですぐに置き換えられるため、式を再度評価する必要はありません。


0

また、Valは値による評価です。つまり、定義中に右側の式が評価されます。Defは名前による評価です。使用するまで評価されません。


0

上記の役立​​つ回答に加えて、私の発見は次のとおりです。

def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int

def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int

def test3(): Int = 4
--test3: test3[]() => Int

上記は、「def」が呼び出されたときに別の関数「Int => Int」を返す(引数パラメーターがゼロの)メソッドであることを示しています。

メソッドから関数への変換については、https//tpolecat.github.io/2014/06/09/methods-functions.htmlで詳しく説明しています


0

REPLでは、

scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean

scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8

defとは call-by-name、オンデマンドで評価される

valはcall-by-value、初期化中に評価されます


この古い質問では、すでに多くの回答が提出されているので、既存の回答で提供されている情報と回答がどのように異なるか、または追加されているかを説明すると役立ちます。
jwvh
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.