IT story

스칼라에서 이름으로 전화 및 값으로 전화, 설명 필요

hot-time 2020. 4. 22. 08:10
반응형

스칼라에서 이름으로 전화 및 값으로 전화, 설명 필요


내가 알기로, 스칼라에서 함수는

  • 값별 또는
  • 이름으로

예를 들어, 다음과 같은 선언을 통해 함수가 어떻게 호출되는지 알고 있습니까?

선언:

def  f (x:Int, y:Int) = x;

요구

f (1,2)
f (23+55,5)
f (12+3, 44*11)

규칙은 무엇입니까?


제시 한 예제는 값별 호출 만 사용하므로 차이점을 보여주는 새롭고 간단한 예제를 제공합니다.

먼저 부작용이있는 함수가 있다고 가정 해 봅시다. 이 함수는 무언가를 출력 한 다음를 반환합니다 Int.

def something() = {
  println("calling something")
  1 // return value
}

이제 Int하나는 값으로 호출 스타일 ( x: Int)로, 다른 하나는 이름으로 호출 스타일 ( )로 인수를 취하는 것을 제외하고는 정확히 동일한 인수 를 허용하는 두 함수를 정의 할 것입니다 x: => Int.

def callByValue(x: Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

이제 부작용 기능으로 호출하면 어떻게됩니까?

scala> callByValue(something())
calling something
x1=1
x2=1

scala> callByName(something())
calling something
x1=1
calling something
x2=1

따라서 값별 호출 버전에서는 전달 된 함수 호출 ( something()) 의 부작용 이 한 번만 발생 했음을 알 수 있습니다 . 그러나 이름 별 통화 버전에서는 부작용이 두 번 발생했습니다.

값별 호출 함수는 함수를 호출하기 전에 전달 된 표현식의 값을 계산하므로 매번 동일한 값에 액세스하기 때문입니다. 그러나 이름 별 호출 함수 는 전달 될 때마다 전달 된 표현식의 값을 재 계산 합니다.


다음은 Martin Odersky의 예입니다.

def test (x:Int, y: Int)= x*x

우리는 평가 전략을 조사하고 다음 조건에서 어느 것이 더 빠른지 (더 적은 단계)를 결정하려고합니다.

test (2,3)

값으로 호출 : test (2,3)-> 2 * 2-> 4
이름으로 호출 : test (2,3)-> 2 * 2-> 4
결과는 동일한 단계 수로 도달합니다.

test (3+4,8)

값으로 전화 : test (7,8)-> 7 * 7-> 49
이름으로 전화 : (3 + 4) (3 + 4)-> 7 (3 + 4)-> 7 * 7-> 49
여기 전화 가치면에서 빠릅니다.

test (7,2*4)

값으로 전화 : test (7,8)-> 7 * 7-> 49
이름으로 전화 : 7 * 7-> 49
여기에서 이름으로 전화하는 것이 더 빠릅니다

test (3+4, 2*4) 

값으로 호출 : test (7,2 * 4)-> test (7, 8)-> 7 * 7-> 49
이름으로 호출 : (3 + 4) (3 + 4)-> 7 (3 + 4) -> 7 * 7-> 49
같은 단계에서 결과에 도달합니다.


예제의 경우 모든 매개 변수는 value 로만 정의 하기 때문에 함수에서 호출 되기 전에 평가 됩니다 . 이름으로 매개 변수를 정의 하려면 코드 블록을 전달해야합니다.

def f(x: => Int, y:Int) = x

이런 식으로 매개 변수 x함수에서 호출 될 때까지 평가되지 않습니다 .

여기 의이 작은 포스트 는 이것도 훌륭하게 설명합니다.


위의 의견에서 @Ben의 요점을 반복하려면 "이름 별 전화"를 단순한 구문 설탕으로 생각하는 것이 가장 좋습니다. 파서는 표현식을 익명 함수로 래핑하여 나중에 사용될 때 호출 할 수 있습니다.

사실상, 정의하는 대신

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

그리고 달리기 :

scala> callByName(something())
calling something
x1=1
calling something
x2=1

당신은 또한 쓸 수 있습니다 :

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}

같은 효과를 얻으려면 다음과 같이 실행하십시오.

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1

예제를 제공하는 것이 아니라 간단한 사용 사례로 설명하려고합니다.

시간이 지날 때마다 매번 잔소리를 하는 "nagger 앱" 을 만들고 싶다고 상상해보십시오 .

다음 구현을 조사하십시오.

object main  {

    def main(args: Array[String]) {

        def onTime(time: Long) {
            while(time != time) println("Time to Nag!")
            println("no nags for you!")
        }

        def onRealtime(time: => Long) {
            while(time != time) println("Realtime Nagging executed!")
        }

        onTime(System.nanoTime())
        onRealtime(System.nanoTime())
    }
}

위의 구현에서 Nagger는 이름으로 전달할 때만 작동합니다. 이유는 값으로 전달할 때 다시 사용되므로 이름으로 전달할 때마다 값을 다시 평가하지 않고 값을 다시 평가하지 않기 때문입니다. 변수에 액세스 한 시간


일반적으로 함수의 매개 변수는 값별 매개 변수입니다. 즉, 매개 변수 값은 함수에 전달되기 전에 결정됩니다. 그러나 함수 내에서 호출 될 때까지 평가하고 싶지 않은 표현식을 매개 변수로 받아들이는 함수를 작성해야하는 경우 어떻게해야합니까? 이러한 상황에서 Scala는 이름 별 호출 매개 변수를 제공합니다.

이름 별 호출 메커니즘은 코드 블록을 수신자에게 전달하고 수신자가 매개 변수에 액세스 할 때마다 코드 블록이 실행되고 값이 계산됩니다.

object Test {
def main(args: Array[String]) {
    delayed(time());
}

def time() = {
  println("Getting time in nano seconds")
  System.nanoTime
}
def delayed( t: => Long ) = {
  println("In delayed method")
  println("Param: " + t)
  t
}
}
 1. C : /> scalac Test.scala 
 2. 스칼라 테스트
 3. 지연된 방법
 4. 나노초로 시간 얻기
 5. 매개 변수 : 81303808765843
 6. 나노초로 시간 얻기

내가 가정 call-by-value한 것처럼 위에서 설명한 함수는 값을 함수에 전달합니다. 에 따르면 Martin Odersky이 기능 평가에서 중요한 역할을 스칼라에 의한 평가 전략의 후속이다. 그러나 간단하게하십시오 call-by-name. 그것과 마찬가지로 함수를 메소드의 인수로 전달하는 것으로도 알려져 Higher-Order-Functions있습니다. 메소드가 전달 된 매개 변수의 값에 액세스하면 전달 된 함수의 구현을 호출합니다. 아래:

@dhg 예제에 따르면, 먼저 다음과 같이 메소드를 작성하십시오.

def something() = {
 println("calling something")
 1 // return value
}  

이 함수는 하나의 println명령문을 포함 하고 정수 값을 리턴합니다. 다음과 같은 인수를 가진 함수를 작성하십시오 call-by-name.

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

이 함수 매개 변수는 하나의 정수 값을 반환하는 익명 함수를 정의합니다. 여기 x에는 0인수를 전달했지만 반환 int값과 something함수에 동일한 서명 포함 된 함수의 정의가 포함됩니다. 함수를 호출하면에 함수를 인수로 전달합니다 callByName. 그러나 그 경우 call-by-value정수 값만 함수에 전달합니다. 다음과 같이 함수를 호출합니다.

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

이 우리에서 something우리의 가치를 액세스 할 때 때문에 방법, 두 번 호출 xcallByName방법의 (고화질)과의 호출 something방법.


가치에 의한 호출은 여기에 많은 답변에서 설명하는 일반적인 사용 사례입니다.

이름 별 호출 은 코드 블록을 호출자에게 전달하고 호출자가 매개 변수에 액세스 할 때마다 코드 블록이 실행되고 값이 계산됩니다.

아래의 사용 사례를 사용하여 이름별로 더 간단한 방법으로 전화를 시연하려고합니다.

예 1 :

이름별로 호출하는 간단한 예제 / 사용 사례는 기능 아래에 있으며, 기능을 매개 변수로 사용하고 경과 시간을 제공합니다.

 /**
   * Executes some code block and prints to stdout the 
time taken to execute   the block 
for interactive testing and debugging.
   */
  def time[T](f: => T): T = {
    val start = System.nanoTime()
    val ret = f
    val end = System.nanoTime()

    println(s"Time taken: ${(end - start) / 1000 / 1000} ms")

    ret
  }

예 2 :

아파치 스파크 (스칼라 포함)는 이름별로 호출을 사용하여 로깅을 사용Logging 합니다 . 아래 방법에서 지연 여부를 평가 하는 특성참조하십시오log.isInfoEnabled .

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }

다음은 현재 스칼라 코스를 수강하는 동료를 돕기 위해 작성한 간단한 예입니다. 내가 흥미로 웠던 것은 Martin이 강의에서 제시 한 && 질문 답변을 예로 사용하지 않았다는 것입니다. 어쨌든 나는 이것이 도움이되기를 바랍니다.

val start = Instant.now().toEpochMilli

val calc = (x: Boolean) => {
    Thread.sleep(3000)
    x
}


def callByValue(x: Boolean, y: Boolean): Boolean = {
    if (!x) x else y
}

def callByName(x: Boolean, y: => Boolean): Boolean = {
    if (!x) x else y
}

new Thread(() => {
    println("========================")
    println("Call by Value " + callByValue(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


new Thread(() => {
    println("========================")
    println("Call by Name " + callByName(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


Thread.sleep(5000)

코드 출력은 다음과 같습니다.

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================

매개 변수는 일반적으로 값으로 전달되므로 함수 본문에서 대체되기 전에 평가됩니다.

함수를 정의 할 때 이중 화살표를 사용하여 이름으로 매개 변수를 강제로 호출 할 수 있습니다.

// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1

// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)

// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))

// will not terminate, since loop(2) will evaluate. 
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 

인터넷에는이 질문에 대한 환상적인 답변이 이미 많이 있습니다. 누군가 주제가 도움이 될 수있는 경우를 대비하여 내가 주제에 관해 수집 한 여러 가지 설명과 예를 편집하여 작성하겠습니다.

소개

가치 별 통화 (CBV)

일반적으로 함수에 대한 매개 변수는 값별 호출 매개 변수입니다. 즉, 매개 변수는 왼쪽에서 오른쪽으로 평가되어 함수 자체가 평가되기 전에 값을 결정합니다.

def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7

CBN (Call-by-Name)

그러나 함수 내에서 호출 될 때까지 평가하지 않는 식을 매개 변수로 받아들이는 함수를 작성해야하는 경우 어떻게해야합니까? 이러한 상황에서 Scala는 이름 별 호출 매개 변수를 제공합니다. 매개 변수가 그대로 함수에 전달되고 대체 후 평가가 수행됨을 의미

def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7

이름 별 호출 메커니즘은 코드 블록을 호출에 전달하고 호출이 매개 변수에 액세스 할 때마다 코드 블록이 실행되고 값이 계산됩니다. 다음 예제에서 delayed는 메소드가 입력되었음을 나타내는 메시지를 인쇄합니다. 다음으로 delayed는 값을 가진 메시지를 인쇄합니다. 마지막으로 지연은 't'를 반환합니다.

 object Demo {
       def main(args: Array[String]) {
            delayed(time());
       }
    def time() = {
          println("Getting time in nano seconds")
          System.nanoTime
       }
       def delayed( t: => Long ) = {
          println("In delayed method")
          println("Param: " + t)
       }
    }

지연된 방법
으로 나노초 단위의 시간 얻기
매개 변수 : 2027245119786400

각 사례의 장단점

CBN : + 더 자주 종료 * 위의 종료 확인 * * 함수 본문의 평가에서 해당 매개 변수를 사용하지 않으면 함수 인수가 평가되지 않는다는 이점이 있습니다. 로드 시간이 길어지고 더 많은 메모리를 소비합니다.

CBV : + CBN보다 기하 급수적으로 더 효율적입니다. 이름으로 호출되는 인수 표현식의 반복 계산을 피하기 때문입니다. 모든 함수 인수를 한 번만 평가 + 명령이 평가 될 때 훨씬 더 잘 아는 경향이 있기 때문에 명령 효과와 부작용으로 훨씬 잘 재생됩니다. -파라미터 평가시 루프가 발생할 수 있습니다.

종료가 보장되지 않으면 어떻게됩니까?

식 e의 CBV 평가가 종료되면 e의 CBN 평가도 종료됩니다.-다른 방향은 사실이 아닙니다.

비 종료 예

def first(x:Int, y:Int)=x

먼저 표현식을 고려하십시오 (1, loop)

CBN : first (1, loop) → 1 CBV : first (1, loop) →이 표현식의 인수를 줄입니다. 하나는 루프이기 때문에 인수를 무한정 줄입니다. 종료되지 않습니다

각각의 사례 행동의 차이점

방법 테스트를 정의 해 봅시다

Def test(x:Int, y:Int) = x * x  //for call-by-value
Def test(x: => Int, y: => Int) = x * x  //for call-by-name

사례 1 테스트 (2,3)

test(2,3)   →  2*2 → 4

이미 평가 된 인수로 시작하므로 값별 호출 및 이름 별 호출에 대해 동일한 단계가됩니다.

사례 2 테스트 (3 + 4,8)

call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49

이 경우 값별 호출은 더 적은 단계를 수행합니다.

Case3 테스트 (7, 2 * 4)

call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49

우리는 두 번째 인수의 불필요한 계산을 피합니다

Case4 테스트 (3 + 4, 2 * 4)

call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 →  49

다른 접근법

먼저 부작용이있는 함수가 있다고 가정 해 봅시다. 이 함수는 무언가를 출력 한 다음 Int를 반환합니다.

def something() = {
  println("calling something")
  1 // return value
}

이제 우리는 하나의 값을 호출하는 스타일 (x : Int)로 다른 하나를 호출하고 다른 하나는 이름을 부르는 스타일 (x : => Int).

def callByValue(x: Int) = {
  println("x1=" + x)
  println("x2=" + x)
}
def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

이제 부작용 기능으로 호출하면 어떻게됩니까?

scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1

따라서 값별 호출 버전에서는 전달 된 함수 호출 (something ())의 부작용이 한 번만 발생했음을 알 수 있습니다. 그러나 이름 별 통화 버전에서는 부작용이 두 번 발생했습니다.

값별 호출 함수는 함수를 호출하기 전에 전달 된 표현식의 값을 계산하므로 매번 동일한 값에 액세스하기 때문입니다. 그러나 이름 별 호출 함수는 전달 될 때마다 전달 된 표현식의 값을 재 계산합니다.

이름 별 통화를 사용하는 것이 더 나은 예

보낸 사람 : https://stackoverflow.com/a/19036068/1773841

간단한 성능 예 : 로깅.

다음과 같은 인터페이스를 상상해 봅시다.

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}

그리고 다음과 같이 사용하십시오 :

logger.info("Time spent on X: " + computeTimeSpent)

info 메소드가 아무것도 수행하지 않으면 (예를 들어, 로깅 레벨이 해당 레벨보다 높게 구성 되었기 때문에), computeTimeSpent가 호출되지 않으므로 시간이 절약됩니다. 이것은 로거에서 많이 발생하며, 로그 작업에 비해 문자열 조작이 많은 경우가 많습니다.

정확성 예 : 논리 연산자.

아마도 다음과 같은 코드를 보았을 것입니다.

if (ref != null && ref.isSomething)

다음과 같이 && 메소드를 선언한다고 상상해보십시오.

trait Boolean {
  def &&(other: Boolean): Boolean
}

그런 다음 ref가 null 일 때마다 &&에 전달되기 전에 null 참조에서 isSomething이 호출되므로 오류가 발생합니다. 이러한 이유로 실제 선언은 다음과 같습니다.

trait Boolean {
  def &&(other: => Boolean): Boolean =
    if (this) this else other
}

예제를 살펴보면 차이점을 더 잘 이해하는 데 도움이됩니다.

현재 시간을 반환하는 간단한 함수를 정의합시다 :

def getTime = System.currentTimeMillis

이제 우리는 1 초씩 두 번 지연되는 함수를 name으로 정의 할 것입니다 :

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

그리고에 의해 하나의 :

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

이제 각각을 호출하자 :

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

결과는 차이점을 설명해야합니다. 스 니펫은 여기에서 사용할 수 있습니다 .


A의 값에 의해 호출 상기 식의 값은 함수 호출시에 미리 계산하고 특정 값이 해당 함수에 파라미터로서 전달된다. 기능 전체에서 동일한 값이 사용됩니다.

반면 A의 이름으로 호출 표현식 자체 기능을 매개 변수로 전달되고, 이는 단지 특정 파라미터가 호출 될 때마다 기능, 내부 계산된다.

Scala에서 Call by Name과 Call by Value의 차이점은 아래 예제를 통해 더 잘 이해할 수 있습니다.

코드 스 니펫

object CallbyExample extends App {

  // function definition of call by value
  def CallbyValue(x: Long): Unit = {
    println("The current system time via CBV: " + x);
    println("The current system time via CBV " + x);
  }

  // function definition of call by name
  def CallbyName(x: => Long): Unit = {
    println("The current system time via CBN: " + x);
    println("The current system time via CBN: " + x);
  }

  // function call
  CallbyValue(System.nanoTime());
  println("\n")
  CallbyName(System.nanoTime());
}

산출

The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521


The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589

위의 코드 스 니펫에서 CallbyValue (System.nanoTime ()) 함수 호출의 경우 시스템 나노 시간이 미리 계산되고 미리 계산 된 값에 매개 변수가 함수 호출에 전달되었습니다.

그러나 CallbyName (System.nanoTime ()) 함수 호출에서 표현식 "System.nanoTime ())"자체는 함수 호출에 매개 변수로 전달되며 해당 매개 변수가 함수 내부에서 사용될 때 해당 표현식의 값이 계산됩니다. .

매개 변수 x 와 해당 데이터 유형을 구분 하는 => 기호 가있는 CallbyName 함수의 함수 정의를 확인하십시오 . 이 특정 기호는 함수가 이름 유형별로 호출되었음을 나타냅니다.

다시 말해, 함수 별 호출 함수 인수는 함수를 입력하기 전에 한 번 평가되지만 이름 별 함수 인수 호출은 필요할 때만 함수 내에서 평가됩니다.

도움이 되었기를 바랍니다!


CallByName사용될 때 callByValue호출되고 명령문이 발생할 때마다 호출됩니다.

예를 들면 다음과 같습니다.

무한 루프가 있습니다. 예를 들어이 함수를 실행하면 scala프롬프트 가 표시되지 않습니다 .

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

callByName기능 이상 소요 loop인수로 방법 및 그것의 본체 내에서 사용되지 않는다.

scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int

callByName메소드 실행시 scala함수 내부에서 루프 함수를 사용하는 곳이 없으므로 문제가 없습니다 ( 프롬프트가 나타납니다) callByName.

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

callByValue기능 이상 소요 loop하여이 외부 함수를 실행하기 전에 평가 기능 또는 발현 내측 결과 파라미터로 방법 loop함수 재귀 실행 우리는 결코 scala프롬프트 백.

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))

이것 좀 봐:

    object NameVsVal extends App {

  def mul(x: Int, y: => Int) : Int = {
    println("mul")
    x * y
  }
  def add(x: Int, y: Int): Int = {
    println("add")
    x + y
  }
  println(mul(3, add(2, 1)))
}

y : => Int는 이름으로 호출됩니다. 이름으로 전화로 전달되는 것은 add (2, 1)입니다. 이것은 게으르게 평가 될 것입니다. 따라서 add가 먼저 호출되는 것처럼 보이지만 콘솔의 출력은 "mul"다음에 "add"가됩니다. 이름 별 호출은 함수 포인터를 전달하는 일종의 역할을합니다.
이제 y : => Int에서 y : Int로 변경하십시오. 콘솔에 "add"와 "mul"이 표시됩니다! 일반적인 평가 방법.


나는 여기에있는 모든 대답이 올바른 정당화를한다고 생각하지 않습니다.

값별로 호출하면 인수는 한 번만 계산됩니다.

def f(x : Int, y :Int) = x

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

위의 모든 인수가 필요한지 여부에 따라 평가되는 것을 알 call-by-value수 있습니다. 일반적 으로 빠를 수는 있지만 항상이 경우와 같은 것은 아닙니다.

평가 전략 call-by-name이었다면 분해 다음과 같습니다.

f(12 + 3, 4 * 11)
12 + 3
15

위에서 볼 수 있듯이 우리는 평가할 필요가 없었기 4 * 11때문에 때때로 유익 할 수있는 약간의 계산을 저장했습니다.

참고 URL : https://stackoverflow.com/questions/13337338/call-by-name-vs-call-by-value-in-scala-clarification-needed

반응형