ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 프로그래머를 위한 Scala 따라해 보기 #4
    기술 관련/Scala 2021. 1. 25. 02:38

    지난 번 따라해보기Java 프로그래머를 위한 Scala 따라해 보기 #3에 이어서 계속 따라가 보도록 하겠다.


    docs.scala-lang.org/ko/tutorials/scala-for-java-programmers.html

     

    자바 프로그래머를 위한 스칼라 튜토리얼

    Michel Schinz, Philipp Haller 지음. 이희종 (heejong@gmail.com) 옮김. 시작하면서 이 문서는 Scala 언어와 그 컴파일러에 대해 간단히 소개한다. 어느 정도의 프로그래밍 경험이 있으며 Scala를 통해 무엇을 할

    docs.scala-lang.org

    지난번 따라하기를 하다가 못알아봤던 Trait을 알아보자.

    트레잇 (Trait)

    trait의 사전적 의미는 특징, 특색이다. 스칼라 문서에서는 다음과 같이 정의하고 있다.

    Traits are used to share interfaces and fields between classes. They are similar to Java 8’s interfaces. Classes and objects can extend traits, but traits cannot be instantiated and therefore have no parameters.

    음.. 뭔가 설명만으로는 잘 이해가 가지 않는다. 우선 Java의 Interface와 비슷하다고 하니 거기서 출발해보자.

     

    Interface는 특정한 기능을 수행하는 것을 정의하고 이를 구현한 클래스를 별도로 구분할 수 있게 해 준다. 엄밀한 형태의 부모-자식과 같은 상속 개념은 아니기 때문에, 이를 구현한 클래스에게 특정한 자격을 부여하는 셈이다. Interface는 클래스가 아니므로, 하나의 클래스가 여러 개의 interface를 구현이 가능하다. 따라서, 클래스 다중 상속이 안되는 Java에서는 이를 유용하게 활용 할 수 있다.

     

    Scala Trait도 Interface 처럼 생겼다. 아니 오히려 여느 Object나 Class처럼 생겼는고 Java Interface에서는 method만 정의 할 수 있는 것에 비해 Trait에서는 field도 정의 할 수 있다는 차이가 있다. 생긴 것이 일반적인 Object나 Class 처럼 생겼지만, Java interface와 마찬가지로 trait 자체만으로는 인스턴스화를 할 수 없고 이를 상속 받은 class나 object를 통해서만 가능하다.

     

    튜토리얼의 예제를 보자.

    trait Ord {
      def < (that: Any): Boolean
      def <=(that: Any): Boolean =  (this < that) || (this == that)
      def > (that: Any): Boolean = !(this <= that)
      def >=(that: Any): Boolean = !(this < that)
    }

     

    Ord는 Trait으로서 값을 비교하는 method 들( '<', '<=', '>', '>=' )을 정의하고 있다. abstract 클래스 처럼 method 자체에 대한 code 블럭이 있는 경우 이에 대한 구현도 가능하지만, 이것으로 new instance를 생성 할 수 없다.

     

    이 Trait을 extends 했을 때와, abstract를 extends 한 경우 무슨 차이가 있을까? 구현 자체로만 놓고 보았을 땐 차이가 없다.

    abstract class Ord {
      def < (that: Any): Boolean
      def <=(that: Any): Boolean =  (this < that) || (this == that)
      def > (that: Any): Boolean = !(this <= that)
      def >=(that: Any): Boolean = !(this < that)
    }

    하지만, abstract으로 구성할 경우 이를 상속받는 클래스는 하나이다. 즉, Scala에서도 Java에서 처럼 클래스는 다중 상속이 되지 않기 때문에, abstract class가 두 개인 경우에 대한 직접적 상속이 불가능하다. 이 때 abstract가 아닌 trait으로 정의하면, Mix-In 조합을 이용하여 멤버를 재정의하여 다중 상속과 같은 효과를 줄 수 있다.

     

    여기서 재밌는 것은 abstract 클래스를 부모 클래스로 둔 trait도 가능하다는 점이다. 

    abstract class AbsIterator {
      type T
      def hasNext: Boolean
      def next(): T
    }
    
    trait RichIterator extends AbsIterator {
      def foreach(f: T => Unit) { while (hasNext) f(next()) }
    }

     

    다시 튜토리얼로 돌아와서 이 Ord를 extend 하는 Date를 클래스를 만들어보자.

    Date 클래스는 연, 월, 일을 입력 받아 날짜를 나타내는 클래스이며, parameter로 year, month, day 받는다. println() 호출시 오늘 날짜를 표기하기 위해 toString()을 override 했고, 두 값이 서로 같은지를 비교하기 위한 eqauls()를 override 했다. 

    class Date(y: Int, m: Int, d: Int) extends Ord {
      def year = y
      def month = m
      def day = d
      override def toString(): String = year + "-" + month + "-" + day
      override def equals(that: Any): Boolean =
        that.isInstanceOf[Date] && {
          val o = that.asInstanceOf[Date]
          o.day == day && o.month == month && o.year == year
        }
    }

    이 상태에서 scalac 로 build하면 오류가 발생하는데, 앞서 Ord에 '<' 메소드는 상속 받는 클래스에서 구현해야 한다.

    class Date(y: Int, m: Int, d: Int) extends Ord {
      def year = y
      def month = m
      def day = d
      override def toString(): String = year + "-" + month + "-" + day
      override def equals(that: Any): Boolean =
        that.isInstanceOf[Date] && {
          val o = that.asInstanceOf[Date]
          o.day == day && o.month == month && o.year == year
        }
    
      def <(that: Any): Boolean = {
        if (!that.isInstanceOf[Date])
          sys.error("cannot compare " + that + " and a Date")
    
        val o = that.asInstanceOf[Date]
        (year < o.year) ||
        (year == o.year && (month < o.month ||
                           (month == o.month && day < o.day)))
      }
    }

    제네릭 (Generic)

    Java 클래스에서는 보통 클래스 내부에서 자료형을 소스 코드 작성 시점에 미리 정의하고 사용한다. 대부분은 그렇게 하지만, Generic의 경우는 이를 컴파일 시점이 아닌 인스턴스 생성 시점에 해당 자료 형을 결정해서 사용하는 것을 말한다. Java5 (JDK1.5) 부터 도입되었다.

     

    Scala에서도 Generic을 사용할 수 있다. 

     

    튜토리얼의 예제를 보자.

    class Reference[T] {
      private var contents: T = _
      def set(value: T) { contents = value }
      def get: T = contents
    }

    뭔가 Java의 Generic과 비슷하지만 뭔가 살짝 느낌이 다르다.  그렇지만 T 라는 타입을 Reference 클래스를 인스턴스화 할 때 지정하면 관련된 클래스가 생성되는 형태라고 이해 할 수 있다. Generic Class를 사용하는 예제 코드는 다음과 같다.

    object IntegerReference {
      def main(args: Array[String]): Unit = {
        val cell = new Reference[Int]
        cell.set(13)
        println("Reference contains the half of " + (cell.get * 2))
      }
    }

    Reference 클래스를 초기화 할 때 Int 타입을 넣었더니 set() 메소드를 통해 '13'이라는 정수형 값을 바로 입력하고 get() 메소드를 통해 값을 읽어 출력하는 예제이다.

     

    이를 Java의 Generic으로 표현해 보면 다음과 같은데, Scala에서 '[T]'로 표현되는 Generic이 Java에서는 '<T>'로 표현되는 차이 정도만 느낄 수 있다. 역시나 Scala 코드가 더 간결해 보인다. 

    class Reference<T> {
      private T contents;
      void set(T value) {
        contents = value;
      }
      T get() {
        return contents;
      }
    }

    Reference 클래스를 사용하는 경우 코드이다.

    class IntegerReference {
      public static void main(String[] args) {
        Reference cell = new Reference<Integer>();
        cell.set(13);
        System.out.println("Reference contains the half of " + (((Integer)cell.get()) * 2));
      }
    }

    여기서 Integer로  cast를 해 준 이유는 Java의 Generic에서는 parameter가 unbound인 경우, 즉 parameter의 범위가 한정(bounds) 되어 있지 않는 경우에는 코드를 compile 하면서 Type을 Object로 바꿔놓기 때문이다. 따라서, Runtime 시 cell.get()을 호출하여 얻어진 결과의 type이 Integer가 아니라 Object가 된다. 그러므로 Integer 클래스로 cast 해야 정수 값으로 인식하고 정상 실행된다.

    따라해보기를 마치며

    사실 JVM은 말그대로 Java VM이다. Java라는 언어가 곧 JVM인 상황에서 언어로서 Java가 가진 제약점을 개선하고 더 높은 생산성을 가진 코드로 표현될 수 있다는게 재밌게 느껴졌다. 특히, 개인적으로는 최근에는 Java 보다 JavaScript나 Python을 주로 사용했기 때문에 Java의 세미 콜론 누락으로 발생하는 컴파일 에러로 이어지는 귀찮음은 스칼라의 존재감이 더욱 확실하게 다가왔다.

     

    Scala에 대해서 잘 모르는 상태에서 맛보기로 접해본 거라 좀 더 자세히 들여다 보고 싶은 마음이 들었는데, "Java 프로그래머를 위한 Scala 튜토리얼" 문서 마지막에도 나와 있는 것 처럼, Scala의 기능을 간략히 소개하는 투어 문서도 참고하며 보아야겠다.

    댓글

Designed by Tistory.