'2016/09/23'에 해당되는 글 1건

  1. 2016.09.23 [scala] class 4 - Any/AnyRef, 커링(curring), 특화(specialization)




스칼라에서는 Any 객체가 상위에 있고, AnyRef와 AnyVal로 나눠진다. 그리고, Nothing과 Null의 세계가 나눠져 있다. 
AnyVal은 주로 프리미티브 타입 쪽으로 관련되어 있다. 이는 값 클래스(Value Class)로 되어 스칼라 컴파일러가 최적화할 수 있는 기법으로 사용된다. 








일반 클래스는 기본적으로 AnyRef를 상속받는다. 사실 extends가 있으나 없으나 동일하다. 자바의 Object와 동일하기 때문이다. 



간단한 케이스 클래스 예시로 이해해보자. 

case class Man(name: String) extends AnyRef {
def canEqual(that: Any): Boolean = that.isInstanceOf[Man]

override def equals(obj: scala.Any): Boolean = super.equals(obj)

override def hashCode(): Int = super.hashCode()
}

object Main extends App {
var i: AnyRef = new AnyRef
println(i)

val samuel = Man("Samuel")
val matt = Man("Matt")
println(samuel.equals(matt))
println(samuel.canEqual(matt))
println(samuel.hashCode)
}



AnyRef 객체를 출력하면, Object가 출력된다. Man 클래스는 AnyRef에 있는 메소드 일부를 override해서 사용했다.

결과는 다음과 같다.


java.lang.Object@3a03464

false

true

759156157







이제 커링을 살펴본다.


커링은 타입 추론을 지원하는 스칼라의 함수 기능이다. 


여러 개의 매개변수가 가지고 있는 함수를 

매개변수가 하나인 함수로 나눌 수 있다. 


f (a, b) = c라는 함수 f가 있다고 가정하자.


F (a) = G,  G(b) = c 가 되는 함수로 만들 수 있다. 이 때 함수 F를 f의 커리라 한다. 

함수의 부분집합 같은 개념이라 할 수 있다.

예시를 살펴본다.


Numbers의 add 메소드는 일반적인 메소드이다. 이를 커링으로 만들어봤다. 
object Numbers {
def add(a: Int, b: Int) : Int = a + b

def addCurrying1(a: Int)(b: Int) : Int = a + b

def addCurrying2(a: Int) : Int => Int = b => a + b

def addCurrying3(a: Int) = (b: Int) => a + b
}

테스트 코드를 실행해 본다.

import com.google.Numbers._

println(add(1, 2))
println(addCurrying1(1)(2))
println(addCurrying2(1)(2))
println(addCurrying3(1)(2))

결과는 모두 3이다. 


커링을 잘 이해하기 위해서 좀 더 테스트를 진행한다. 커링 함수의 두 번째 매개변수 에 대한 위치 표시자로서, 매개변수를 받아 기존 값에 더하는 함수에 대한 참조 함수를 출력한다. 
println(addCurrying1 _)
println(addCurrying1(1) _)
결과는 다음과 같다.
<function1>
<function1>


하지만, addCurring2와 addCurring3 함수는 ()() 형태가 아니고 () 형태라서 런타임 에러가 발생한다. 

println(addCurrying2 _) // <function1> 출력
//println(addCurrying2(1) _)
//Error:(31, 23) _ must follow method; cannot follow Int => Int


println(addCurrying3 _) // <function1> 출력
//println(addCurrying3(1) _)
//Error:(31, 23) _ must follow method; cannot follow Int => Int


이번에는 부분함수처럼 사용해본다. addCurrying1 메소드는 ()() 형태 이기 때문에 부분함수로 받으려면 에러가 발생한다.
//val f1 = addCurrying1(1)
// Error:(27, 24) missing argument list for method addCurrying1 in object Numbers
//Unapplied methods are only converted to functions when a function type is expected.
//You can make this conversion explicit by writing `addCurrying1 _` // or `addCurrying1(_)(_)` instead of `addCurrying1`.
하지만, addCurrying2, addCurring3는 부분함수처럼 쓸 수 있다. 


val f2 = addCurrying2(1)
val f3 = addCurrying3(1)

val g2 = f2(2)
val g3 = f3(2)

assert(g2 == 3)
assert(g3 == 3)



커링을 언제 쓰나 봤더니, foldRight, reduceLeft, reduceRight, foldLeft 메소드에서 커링 함수를 사용하고 있다. 


override /*IterableLike*/
def foldRight[B](z: B)(@deprecatedName('f) op: (A, B) => B): B =
if (this.isEmpty) z
else op(head, tail.foldRight(z)(op))

override /*TraversableLike*/
def reduceLeft[B >: A](f: (B, A) => B): B =
if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft")
else tail.foldLeft[B](head)(f)

override /*IterableLike*/
def reduceRight[B >: A](op: (A, B) => B): B =
if (isEmpty) throw new UnsupportedOperationException("Nil.reduceRight")
else if (tail.isEmpty) head
else op(head, tail.reduceRight(op))


또한 retry하는 대표적인 예제에도 커링이 들어간다. 


def retry[T](n: Int)(fn: => T): T = {
  try {
    fn
  } catch {
    case e if n > 1 =>
      retry(n - 1)(fn)
  }
}







특화(specialization)은 제네릭 사용시 type erasure로 인해 boxing, unboxing이 발생할 수 있다. 예를 들어, fill 메소드에 엄청큰 수를 넣고 map 함수를 사용하면(에, List.fill(50000)(2).map(_* 3) ), 엄청난 boxing, unboxing이 일어나 성능이 저하될 수 있다. 이를 방지하기 위해서 나온 개념이다.


간단한 예시를 살펴본다. 


case class Count[T](value: T)

object Main extends App {
def newCount(l: Long): Count[Long] = Count(l)
}

별 것도 아닌 것처럼 보이지만, scalac -print로 확인해보면, 박싱을 진행하고 있다.




def newCount(l: Long): Count = new Count(scala.Long.box(l));




특화를 적용(@specialized 추가)하면 다음과 같다. 

case class Count[@specialized(Long, Int) T](value: T)

object Main extends App {
def newCount(l: Long): Count[Long] = Count(l)
}


scalac -print로 살펴보면, 클래스가 생겼고, boxing이 없는 코드로 바뀌어 있다. 



def newCount(l: Long): Count = new Count$mcJ$sp(l);



  <specialized> class Count$mcI$sp extends Count {

    <paramaccessor> <specialized> protected[this] val value$mcI$sp: Int = _;

    <stable> <accessor> <specialized> def value$mcI$sp(): Int = Count$mcI$sp.this.value$mcI$sp;

    override <stable> <accessor> <specialized> def value(): Int = Count$mcI$sp.this.value$mcI$sp();

    override <synthetic> <specialized> def copy$default$1(): Int = Count$mcI$sp.this.copy$default$1$mcI$sp();

    override <synthetic> <specialized> def copy$default$1$mcI$sp(): Int = Count$mcI$sp.this.value();

    def specInstance$(): Boolean = true;

    override <synthetic> <bridge> <specialized> <artifact> def copy$default$1(): Object = scala.Int.box(Count$mcI$sp.this.copy$default$1());

    override <stable> <bridge> <specialized> <artifact> def value(): Object = scala.Int.box(Count$mcI$sp.this.value());

    <specialized> def <init>(value$mcI$sp: Int): Count$mcI$sp = {

      Count$mcI$sp.this.value$mcI$sp = value$mcI$sp;

      Count$mcI$sp.super.<init>(null);

      ()

    }

  };












Posted by '김용환'
,