ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 프로그래머를 위한 Scala 따라해 보기 #2
    기술 관련/Scala 2021. 1. 20. 17:18

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


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

     

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

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

    docs.scala-lang.org

    Java와 Scala 의 차이 중에서 Anonymous 함수를 빼 놓을 수 없다. 이름에서 알 수 있듯이 Anonymous 즉, 익명이다. 이름을 주지 않았는데 존재하는 함수다. JavaScript나 Python을 먼저 배웠다면 이게 뭐가 이상할까라고 생각하겠지만, Java7 (JDK1.7) 이전에는 클래스를 작성 시 반드시 선언되어야 하고 그러기 위해서는 이름이 필요할 정도였고, Java8가 되어서야 Anonymous 클래스가 도입되었던 개념이다. Scala에서는 이 Anonymous 개념을 함수 단위로 지원한다.

    object TimerAnonymous {
      def oncePerSecond(callback: () => Unit): Unit = {
        while (true) { callback(); Thread sleep 1000 }
      }
      def main(args: Array[String]): Unit = {
        oncePerSecond(() =>
          println("time flies like an arrow..."))
      }
    }

    Anonymous의 개념을 배우기 전에 나와 같이 Java 코드만 보고 Scala 코드는 거의 익숙하지 않아 잘 모르는 사람들은 이게 뭔가.. 뭘 말하는 건지 모르겠다는 생각이 들 것이다. 그래서 먼저 Scala object와 method를 보는 법을 확인해 보았다.

    Scala Object 구조 살펴보기

    구조를 살펴보면 이 코드는 TimerAnonymous라는 object를 정의하는 것으로 mainoncePerSecond라는 이름의 두 개의 method가 정의되어 있다. 

     

    그 중 main 메소드를 자세히 들여다 보자. 사실 main 은 이전 글의 HelloWorld에서도 보았던 형식이고, 그냥 실행 만 해보고 잘 돌아가니 그러련 하면서 그 구조는 확인하지 않았었다.

     

    def main(args: Array[String]): Unit = {
      println("Hello, world!")
    }

    Scala 상수 정의 방법

    스칼라에서는 상수를 정의할 때 다음과 같은 형식을 가진다. 상수 값이므로 val로 선언된 값은 처음 할당된 이후에는 변경할 수 없다.

    val x: Int = 1

    상수 선언을 위한 키워드는 val, 그 이름은 x 그리고 콜론 문자(:) 다음에 나오는 것이 해당 객체의 타입으로 Int. 즉, 정수형이다. 그 다음 '=' 문자에 오는 1은 초기 할당되는 상수 값을 의미한다. 만약 변수를 원한다면 키워드로 var를 이용하면 된다.

     

    Scala Method 정의 방법

    method의 경우도 상수와 비슷한 방법으로 def 라는 키워드를 이용하고, 뒤이어 method 이름, argument를 괄호(parenthesis)로 둘러싼 형태가 된다. 괄호 뒤에 이어 콜론 문자 ':' 가 나온 후 해당 method가 실행 후 얻게 되는 결과의 타입을 지정하고, 마지막으로 '=' 문자 이후 중괄호(curly parenthesis)로 코드 블럭을 정의한다.

     

    다음 예제에서는 main이라는 메소드는 실행 시 args라는 이름을 가진 String 배열 타입의 argument를 받아 실행되고, 실행 후 Unit 타입을 결과를 얻게 되는 함수를 나타내고 있다. 참고로, Unit은 Java의 void와 같은 의미다.

    def main(args: Array[String]): Unit = {
      // 여기가 코드 블럭
      // 코드가 실행되며 리턴되는 값의 타입은 Unit
    }

     

    Scala method의 argument를 정의할 때 Java와 다르게 좀 특이한 점이 있는데, 보통 괄호를 하나를 두고 comma로 구분된 argument들을 추가하는 모습이 일반적인데, Scala에서는 ()를 여러 개 두는 것이 가능하다. 다음 예제를 살펴보자.

     

    def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier
    
    println(addThenMultiply(1, 2)(3)) // 9

    이렇게 정수형 x, y와 multiplier라는 값을 괄호로 구분된 형태로 넘겨 받을 수 있다.

     

    예제가 나온김에 Scala에서는 method 코드 블럭에 return이란 값을 써 주지 않아도 마지막 statement 실행 결과가 method의 return type과 동일한 경우 이를 return 값으로 인식한다.

    def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = {
      (x + y) * multiplier
    }
    
    println(addThenMultiply(1, 2)(3)) // 9
    def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = {
      (x + y) * multiplier
      1
    }
    
    println(addThenMultiply(1, 2)(3)) // 1

     

    Scala는 보면 볼수록 재있는 면이 상당히 보인다. ㅎㅎ 물론 return 타입이 정의된 것과 다르다면 컴파일 단계에서 오류를 출력한다.

    말이 나온김에 main method의 함수의 return type인 Unit이 경우는 void와 같은 것이라 앞서 언급했었다. 즉, return 되는 값이 없는 경우를 의미하는 것다. 만약, Unit으로 return 되는 method를 print하는 형태라면 단순하게 '()' 가 출력되며, 그 결과가 없는 상태가 실행되었다는 뜻으로 볼 수 있다.

     

    Scala에서 함수와 메소드의 차이

    그렇다면 function은 뭘까? Scala에서는 method 와 function 을 구분하고 있다. 먼저 function은 정의된 parameter를 통해 argument 를 넘겨 받아 실행되는 코드 블럭을 말한다. 여기까지만 들어보면 method와 무슨 차이가 있을까 생각할 수도 있다. Scala에서 method와 function을 구분하는 방법은 바로 이름이다. method는 반드시 이름이 있지만, function은 이름이 없다. Anonymous의 개념이 다시 나오는 순간이다.

     

    다음의 표현을 보자.

    (x: Int) => x + 1

    이는 정수형 parameter를 x라는 이름으로 받고, 실행 결과 값으로 앞서 받은 x에 1을 더하여 반환한다. 추가적으로 코드 블럭을 할당하는 directive가 method의 경우는 '='였지만, function은 '=>'를 이용한다.

     

    function은 단지 이름이 자체가 없을 뿐 Object이므로 이에 Object에 대해 val이나 var로 이름을 붙여 줄 수 있다.

    val addOne = (x: Int) => x + 1

    단, function은 어딘가의 코드 블럭에서 선언 되어야 하므로 object method와 같은 공간에서 선언거나 코드 블럭 중간에 해당 함수를 작성하여 사용한다.

    object Main {
      def main(args: Array[String]): Unit = {
        var addOne = (x: Int) => x + 1
        println(addOne(5)) // 6
      }
    }

     

    Anonymous 함수를 적용한 예제 

    좀 멀리 돌아오긴 했는데 이제 튜토리얼에 나왔던 TimerAnonymous object를 다시 보자

    object TimerAnonymous {
      def oncePerSecond(callback: () => Unit): Unit = {
        while (true) { callback(); Thread sleep 1000 }
      }
      def main(args: Array[String]): Unit = {
        oncePerSecond(() =>
          println("time flies like an arrow..."))
      }
    }

    main method에서 oncePerSecond라는 method를 호출하면서 argument로 넘긴 것이 바로 function이다. code block이 단일 statement 밖에 없으므로 중괄호가 생략되었고, function의 이름이 없어 anonymous function이 적용된 상태이다.

     

    () => println("time flies like an arrow..."))

     

    함수를 parameter로 사용한다고?

    앞서, function도 object로 인식한다고 이야기 했었다. 그 말은 곧 parameter로 전달 할 수 있다는 말이다. oncePerSecond() method의 parameter를 보자. 이름이 callback인데, main에서 넘겨주는 argument 형식이 "() => Unit"으로 표현되어 있다. 이는 parameter가 없고 return 값도 없는 function 타입을 parameter로 사용한다는 것을 의미한다.

    def oncePerSecond(callback: () => Unit): Unit = {
      //
    }

     

    만약 callback으로 전달되는 값에 addOne이나 addThenMultiply와 같이 parameter와 결과 값을 갖는 경우라면 어떨까?

    object Main {
    
      def anonymousHandler(callback: Int => Int): Unit = {
        print(callback(5))
      }
    
      def main(args: Array[String]): Unit = {
        println("Hello world!")
        anonymousHandler((x: Int) => x + 1) // addOne
      }
    }

    위와 같이 callback으로 parameter를 전달할 수 있다. 두 개의 parameter를 정의하는 경우라면 이렇게 표현해 볼 수도 있다.

    object Main {
    
      def anonymousHandler(callback: (Int, Int) => Int): Unit = {
        print(callback(1,2))
      }
    
      def main(args: Array[String]): Unit = {
        println("Hello world!")
        val add = (x: Int, y: Int) => x + y
        anonymousHandler(add)
      }
    }

    callback으로 넘겨지는 function의 parameter는 그 parametr의 타입 정보만 입력하면 된다.

    callback: (Int, Int) => Int

     

    이렇게 작성되는 코드를 보며 Java가 가진 모습과 Scala의 모습이 점점 비교가 되기 시작한다. 정말 Scala의 매력으로 느껴지는데, Scala에 익숙해진다면 Java로 되돌아가기는 쉽지 않을 것 같다.  ㅎㅎ

     

    자, 그럼 나머지는 다음에 따라해보기로~!

    댓글

Designed by Tistory.