함수를 정의하기 위해 "def"와 "val"의 차이점은 무엇입니까


214

차이점은 무엇입니까?

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

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

둘 다처럼 호출 될 수 있습니다 even(10).


안녕, 무슨 Int => Boolean뜻이야? 정의 구문은 다음과 같습니다def foo(bar: Baz): Bin = expr
Ziu

@Ziu 즉, 'even'함수는 Int를 인수로 받고 부울을 값 유형으로 반환합니다. 따라서 부울 'false'로 평가되는 'even (3)'을 호출 할 수 있습니다
Lobur

@DenysLobur 답장을 보내 주셔서 감사합니다! 이 구문에 대한 참조가 있습니까?
Ziu

@Ziu 나는 기본적으로 Odersky의 코 세라 과정에서 그것을 발견 - coursera.org/learn/progfun1을 . 당신이 그것을 완료 할 때, 당신은 'Type => Type'의 의미를 이해합니다
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

세 번째 옵션이 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여러 통화 보다 성능이 떨어질 수 있습니다 . 한 번의 통화로 동일한 성능을 얻을 수 있습니다. 또한 전화가 없으면에서 오버 헤드 def가 발생하지 않으므로 일부 지점에서 사용하지 않더라도이를 정의 할 수 있습니다.

를 사용하면 lazy val지연 평가를 얻을 수 있습니다. 일부 분기에서 사용하지 않더라도 정의 할 수 있으며 한 번 평가되거나 전혀 평가되지 않지만에 대한 모든 액세스에서 이중 검사 잠금으로 인해 약간의 오버 헤드가 발생합니다 lazy val.

@SargeBorsch가 언급했듯이 메소드를 정의 할 수 있으며 이것이 가장 빠른 옵션입니다.

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

그러나 함수 구성 또는 고차 함수 (예 filter(even):)에 함수 (메소드가 아님)가 필요한 경우 컴파일러는 함수를 함수로 사용할 때마다 메소드에서 함수를 생성하므로 성능이 val.


성능과 관련하여 비교해 주시겠습니까? even호출 될 때마다 함수를 평가하는 것은 중요하지 않습니다 .
Amir Karimi

2
def메소드를 정의하는 데 사용할 수 있으며 이것이 가장 빠른 옵션입니다. @ A.Karimi
표시 이름

2
재미를 위해 : 2.12에 even eq even.
som-snytt

C ++에서와 같은 인라인 함수 개념이 있습니까? 나는 C ++ 세계에서 왔으므로 무지를 용서하십시오.
animageofmine

2
@animageofmine 스칼라 컴파일러는 메소드를 인라인하려고 시도 할 수 있습니다. 이것에 대한 @inline속성 이 있습니다. 그러나 함수 호출은 apply함수 객체의 가상 메소드에 대한 호출이므로 함수를 인라인 할 수 없습니다 . JVM은 일부 상황에서 이러한 호출을 고안하고 인라인 할 수 있지만 일반적으로 그렇지는 않습니다.
세 니아

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메소드 본문을 다시 호출 합니다. 그러나로 even2브로 상기 함수는 선언 (따라서는 인쇄하는 동안 초기화 된 후 val다시는 라인 (4)에서와)과 동일한 출력은 액세스 할 때마다 사용된다. 예를 들어 이것을 시도하십시오 :

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
나는 당신의 설명이 당신이 의도하지 않은 것을 의미한다고 생각합니다. 으로 even2한 번, 한 번으로 두 번 전화 12겁니다. 전화 할 때마다 다른 답변을받습니다. 따라서 println후속 호출 에서는 이 실행되지 않지만에 대한 다른 호출에서 동일한 결과 를 얻지 못합니다 even2. 왜 println다시 실행되지 않는지 는 다른 질문입니다.
melston

1
그것은 실제로 매우 흥미 롭습니다. val, 즉 even2의 경우와 마찬가지로 val은 매개 변수화 된 값으로 평가됩니다. 그렇기 때문에 기능의 가치와 가치를 평가할 수 있습니다. println은 평가 된 값의 일부가 아닙니다. 평가의 일부이지만 평가 된 값이 아닙니다. 여기서의 트릭은 평가 된 값이 실제로 일부 입력에 따라 매개 변수화 된 값이라는 것입니다. 똑똑한 것
MaatDeamon

1
@ 멜 스턴 정확히! 그것이 내가 이해 한 것이므로 출력이 변경되는 동안 println이 다시 실행되지 않는 이유는 무엇입니까?
aur

1
@ 2 짝수에 의해 반환되는 것은 실제로 함수입니다 (짝수 정의의 끝에 괄호로 묶은 식). 이 함수는 실제로 호출 할 때마다 even2에 전달하는 매개 변수로 호출됩니다.
melston

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

즉 "sq"는 스칼라 정의에 의해 수정됩니다. 선언 시점에 바로 평가되므로 나중에 변경할 수 없습니다. 다른 예에서는 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)
       }

우리는 스칼라에서 위의 표현 ({..} 내부)에 대한 마지막 진술이 실제로 왼쪽으로 돌아온다는 것을 알고 있습니다. 따라서 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는 x가 호출 될 때마다 평가됩니다.

또는 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

데프 수단 call-by-name , 요구에 따라 평가

val은 call-by-value초기화 중에 평가됨을 의미합니다 .


이전 질문과 이미 많은 답변이 제출 된 경우, 기존 답변에 제공된 정보와 어떻게 다른지 또는 그 답을 추가하는 방법을 설명하는 것이 도움이됩니다.
jwvh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.