스칼리 :: call by name, call by value

[스칼라] call-by-name, call-by-value

Evalution in Scala

스칼라에서 자주 쓰이지만 햇갈리는 개념이 있어 정리한다.

  • call-by-name
  • call-by-value

값을 계산하는 데 소요되는 시간이 작은 경우에는 위의 차이가 별로 나지 않기 때문에 큰 데이터를 입력받을 때를 기준을 정리하려고 한다.
아래의 예에서는 값을 계산하는 과정을 평가라고도 부를 것이다. 여기에서 등장하는 tree는 백 만개의 노드를 가진 트리 데이터를 집어넣었다.

=> arg vs arg

함수의 인자로 들어가는 모든 값들은 함수가 call되어 body가 실행되기 직전에 값이 계산되어 내부적으로 val로 저장된다.

evalTest (tree : MyTree[Int]) = {
}
//argument evaluation time : 538 ms

인자 tree가 def, val, lazy val 중 어떤 것으로 정의되었던지 함수에 입력된 순간에는 전혀 상관이 없다. 함수로 들어와 함수가 실행되는 순간 평가를 거치게 된다. 또한 한 번 평가가 이루어지면 함수 내부에서는 tree가 val tree처럼 사용된다. 따라서 처음 인자의 값을 계산하는 데 많은 시간을 소비하게 되지만 이 후에는 계산시간이 들지 않는다.

evalTest (tree : => MyTree[Int]) = {
}
// argument evaluation time : 3 ms

위의 함수는 =>을 사용한다. tree의 입력으로 def, val, lazy val 어떤 것이 오더라도 val이 된다고 했지만 => 을 사용하면 계산을 다음으로 미룬다. 즉 내부적으로는 lazy val tree 로 저장하는 것과 같다. argument evaluation time이 크게 줄은 것을 볼 수 있다.

def vs val

def 와 val의 차이는 2 가지가 있다.

  • def x
def evalTest (tree : => MyTree[Int]) : Int = {  
  val t0 = System.nanoTime()  
  def x = tree.iter  
  val t1 = System.nanoTime()  
  println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")
  //evaluation time : 0 ms

argument에서 값의 평가를 한 번 미루었기 때문에 def x 에서 평가되어야 하지만 def를 사용해서 평가를 다음으로 미룬다. 따라서 시간이 거의 들지 않게 된다.

  • val x
def evalTest (tree : => MyTree[Int]) : Int = {  
  val t0 = System.nanoTime()  
  val x = tree.iter  
  val t1 = System.nanoTime()  
  println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")
// evaluation time : 683 ms

argument에서 미룬 값의 평가를 val x에서 해야 하기 때문에 evaluation time이 크게 드는 것을 볼 수 있다.

  • 차이점
    def와 val의 또 다른 차이는 val은 한 번 계산해 놓으면 다음에 사용할 때 계산해 놓은 값을 사용하지만 def는 매번 사용할 때마다 다시 계산한다.

cf)
참고로 입력받을 때 함수 인자를 =>로 받지 않으면 val로 이미 한번 평가가 이루어지기 때문에 def x이든 val x 이든 evaluation time은 0에 가깝게 든다.

def evalTest (tree : MyTree[Int]) : Int = {  
  val t0 = System.nanoTime()  
  val x = tree.iter  
  val t1 = System.nanoTime()  
  println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")
// evaluation time : 1 ms

def vs lazy val

def 와 lazy val의 차이는 간단하다. lazy val과 def는 모두 평가를 다음으로 미룬다는 공통점이 있지만 def가 매번 평가하는 것에 반해 lazy val은 한 번 평가가 일어난 후에 더 이상 평가가 이루어지지 않고 val처럼 행동한다.
lazy val이 처음 평가에서 exception을 일으켰을 때는 다음에 한 번 더 평가가 이루어지기도 한다.

테스트 한 코드


abstract class Iter[A]{  
  def getValue : Option[A]  
  def getNext : Iter[A]  
}  
  
abstract class Iterable[A] {  
  def iter : Iter[A]  
}  
  
  
def sumElement[A] (f : A => Int) (xs : Iter[A]) : Int = {  
  xs.getValue match {  
    case Some(n) => f(n) + sumElement (f) (xs.getNext)  
    case None => 0  
  }  
}  
  
def sumElementGen[A](f : A => Int)(itr : Iterable[A]): Int ={  
  sumElement(f)(itr.iter)  
}  
  
def rev[A](f : MyList[A])(result : MyList[A]) : MyList[A] = {  
  f match {  
    case MyNil() => result  
    case MyCons(hd, tl) => rev(tl)(MyCons(hd, result))  
  }  
}  
  
def concat[A] (f : MyList[A], s : MyList[A]) : MyList[A] = {  
  f match {  
    case MyNil() => s  
    case MyCons(hd, tl) => concat(tl, MyCons(hd, s))  
  }  
}  
  
sealed abstract class MyList[A] extends Iter[A] {  
  def concatenate(l : MyList[A]) = concat(rev(this)(MyNil()), l)  
}  
  
case class MyCons[A](value : A, next : MyList[A] ) extends MyList[A]{  
  def getValue = Some(value)  
  def getNext = next  
}  
case class MyNil[A]() extends MyList[A]{  
  def getValue = None  
  def getNext = throw new Exception ("Null point error")  
}  
  
sealed abstract class MyTree[A] extends Iterable[A] {  
  def iter : MyList[A]  
}  
case class Empty[A]() extends MyTree[A] {  
  val iter : MyList[A] = MyNil()  
}  
case class Node[A](value : A, left: MyTree[A], right: MyTree[A]) extends MyTree[A]{  
  val iter : MyList[A] = MyCons(value, concat[A](left.iter, right.iter))  
}  
  
def generateTree(n: Int) : MyTree[Int] = {  
  def gen(lbnd : Int, ubnd : Int) : MyTree[Int]= {  
    if (lbnd > ubnd) Empty()  
    else {  
      val mid = (lbnd + ubnd) / 2  
  Node(mid, gen(lbnd, mid - 1), gen(mid + 1, ubnd))  
    }  
  }  
  gen(1, n)  
}  
  
  
def time[R] (func : => R) : R = {  
  val t0 = System.nanoTime()  
  val result = func  
  val t1 = System.nanoTime()  
  println("Elapsed time: " + ((t1 - t0)/1000000) + "ms"); result  
}  
  
def sumN[A] (f : A => Int) (xs :Iterable[A]) : Int = {  
  def aux[A](f : A => Int)(x : Iter[A], res : Int) : Int = {  
    x.getValue match {  
      case Some(a) => aux(f)(x.getNext, res + f(a))  
      case None => res  
    }  
  }  
  aux(f)(xs.iter, 0)  
}  
  
def evalTest (tree : MyTree[Int])(st : Long) : Int = {  
  val t0 = System.nanoTime()  
  println("\n\nargument evaluation time : " + ((t0 - st)/1000000) + " ms\n")  
  def x = tree.iter  
  val t1 = System.nanoTime()  
  println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")  
  val et0 = System.nanoTime()  
  if (tree.iter.getValue != None)  
    if(tree.iter.getValue != None)  
      if(tree.iter.getValue != None)  
        println("")  
  tree.iter.getValue match {  
    case Some(n) =>  
      val et1 = System.nanoTime()  
      println("evaluation time : " + ((et1 - et0)/1000000) + " ms\n")  
      n  
    case None =>  
      val et1 = System.nanoTime()  
      println("evaluation time : " + ((et1 - et0)/1000000) + " ms\n")  
      0  
  }  
}  
  
  
def y = generateTree(1000000)  
val start_time = System.nanoTime()  
evalTest(y)(start_time)

댓글

이 블로그의 인기 게시물

[Linux, Unix] export, echo 명령어 알아보기

IEEE 754 부동 소수점 반올림과 근사