ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스칼라 입문] PRELUDE꞉ A TASTE OF SCALA 번역
    스칼라 2022. 1. 9. 20:10

    https://docs.scala-lang.org/overviews/scala-book/prelude-taste-of-scala.html 의 내용을 번역한 것입니다.

     

    들어가며

    예제로 들어가기 전, 스칼라의 주요 특징을 살펴보자면 아래와 같다.

    • 스칼라는 high-level language이다
    • 스칼라는 정적 타입 언어다
    • 스칼라의 문법은 간결하고도 이해하기 쉽다
    • 객체 지향 프로그래밍 (OOP) 패러다임을 지원한다
    • 함수형 프로그래밍 (Functional Programming) 패러다임을 지원한다
    • 정교한 타입 추론 시스템을 갖추고 있다
    • 스칼라 코드는 JVM에서 실행되는 .class 파일을 만든다
    • Java 라이브러리를 사용하기 쉽다

    Hello, World

    새 언어를 배울때 Hello, World로 시작하지 않으면 왠지 섭섭하다. 아래는 스칼라로 Hello, World를 출력하는 예제이다.

    object Hello extends App {
      println("Hello, World")
    }

    위의 내용으로 Hello.scala를 만든 뒤 scalac를 이용하여 컴파일 할 수 있다.

    $ scalac Hello.scala

    자바에 익숙한 사람이라면 javac와 비슷한 거라고 생각하면 된다. 위의 커맨드는 아래 두 파일을 만든다.

    • Hello$.class
    • Hello.class

    위의 파일들은 javac로 만드는 바이트코드 파일과 동일하고 JVM에서 실행 할 수도 있다. 아래 명령어로 Hello 를 실행해보자

    $ scala Hello

     

    The Scala REPL

    스칼라 REPL (Read-Evaluate-Print-Loop)는 스칼라 코드를 편하게 가지고 놀 수 있는 command-line interpreter이다. 스칼라를 설치한 후 터미널에 scala를 입력해서 실행할 수 있다.

    $ scala // 스칼라 REPL 실행
    
    
    
    scala> val x = 1
    x: Int = 1
    
    scala> val y = x + 1
    y: Int = 2

     

    Two types of variables

    스칼라는 두가지 타입의 변수가 있다.

    • val: immutable (변경할 수 없는) 변수이며, 변경할 경우가 없다면 우선적으로 활용해야 할 변수 타입. java final과 유사함.
    • var: mutable (변경 가능한) 변수이며 꼭 필요한 경우에만 활용하는 것이 좋다.

     

    Declaring variable types

    스칼라에서는 종종 변수의 타입을 명시하지 않고 생성하기도 한다. (int 인지, String인지를 명시하지 않음)

    val x = 1
    val s = "a string"
    val p = new Person("Regina")

    스칼라는 타입을 추론할 수 있기 때문에 이렇게 변수를 선언해도 스칼라가 타입을 유추한다.

    scala> val x = 1
    val x: Int = 1 // Int 라고 명시하지 않았는데도 유추하였음
    
    scala> val s = "a string"
    val s: String = a string // String이라고 명시하지 않았는데도 유추하였음
    
    scala> val x: Int = 1 // Int 타입이라고 명시함
    scala> val s: String = "a string" // String 타입이라고 명시함

    이러한 특성을 type inference 라고 하는데 코드를 간결하게 작성하는데 큰 도움이 된다. 거의 필요하지는 않지만 명시적으로 변수의 타입을 선언해 줄 수도 있다.

     

    Control structures

    다음은 흐름 제어에 관련된 스칼라 문법이다.

    if/else

    스칼라의 if/else 구문은 다른 언어와 비슷하다.

    if (test1) {
    	doA()
    } else if (test2) {
        doB()
    } else if (test3) {
        doC()
    } else {
        doD()
    }

    하지만 java나 많은 다른 언어와 달리 if/else 구문은 하나의 값을 리턴한다. 이 점을 이용하면 ternary operator 처럼 활용할 수도 있다.

    val x = if (a < b) a else b

     

    match expressions

    스칼라는 자바의 switch 구문과 유사한 기능을 하는 match expression이 있다.

    val result = i match {
        case 1 => "one"
        case 2 => "two"
        case _ => "not 1 or 2"
    }

    match expression은 integer 뿐만아니라 다양한 타입을 활용하여 작성할 수도 있다.

    val booleanAsString = bool match {
        case true => "true"
        case false => "false"
    }

    아래와 같이 match는 메소드에 활용될 수 있고 많은 타입을 혼용해 사용할 수 도 있다.

    def getClassAsString(x: Any):String = x match {
        case s: String => s + " is a String"
        case i: Int => "Int"
        case f: Float => "Float"
        case l: List[_] => "List"
        case p: Person => "Person"
        case _ => "Unknown"
    }

    강력한 match expression은 스칼라의 주요 특징중 하나이다.

     

    try/catch

    스칼라 역시 예외처리를 위한 try/catch구문을 가지고있다. match 표현식과 함께 쓸 수 있다.

    try {
        writeToFile(text)
    } catch {
        case fnfe: FileNotFoundException => println(fnfe)
        case ioe: IOException => println(ioe)
    }

     

    for loops and expressions

    반복을 위한 for loop은 아래와 같다. 

    for (arg <- args) println(arg)
    
    // "x to y" syntax
    for (i <- 0 to 5) println(i)
    
    // "x to y by" syntax
    for (i <- 0 to 10 by 2) println(i)

    for loop에 yield 키워드를 추가해서 결과를 바로 생성하는식으로 활용할 수도 있다.

    scala> val x = for (i <- 2 to 5) yield i * 10
    val x: IndexedSeq[Int] = Vector(20, 30, 40, 50)

    아래와 같이 반복문에 조건을 추가할 수도 있다.

    val fruits = List("apple", "banana", "lime", "orange")
    
    val fruitLengths = for {
        f <- fruits
        if f.length > 4
    } yield f.length

    일반적인 스칼라 코드는 의미를 바로 이해할 수 있게 만들어졌기 때문에, 이렇게 한번도 본 적 없는 코드여도 어느정도 그 결과를 추측하기가 용이하다.

     

    while and do/while

    또 다른 반복문으로 스칼라는 while과 do/while을 가지고 있다.

    while(condition) {
        statement(a)
        statement(b)
    }
    
    do {
        statement(a)
        statement(b)
    } while(condition)

     

    Classes

    다음은 스칼라 클래스의 예이다.

    class Person(var firstName: String, var lastName: String) {
        def printFullName() = println(s"$firstName $lastName")
    }
    val p = new Person("Julia", "Kern")
    println(p.firstName)
    p.lastName = "Manes"
    p.printFullName()
    
    // 결과
    Julia
    Julia Manes

    아래는 좀 더 복잡한 클래스 예이다.

    class Pizza (
        var crustSize: CrustSize,
        var crustType: CrustType,
        val toppings: ArrayBuffer[Topping]
    ) {
        def addTopping(t: Topping): Unit = toppings += t
        def removeTopping(t: Topping): Unit = toppings -= t
        def removeAllToppings(): Unit = toppings.clear()
    }

     

    Scala methods

    다른 OOP언어와 마찬가지로 스칼라는 method가 있다.

    def sum(a: Int, b: Int): Int = a + b
    def concatenate(s1: String, s2: String): String = s1 + s2

    method의 리턴 타입을 선언 할 필요는 없기 때문에 아래 처럼 생략이 가능하다.

    def sum(a: Int, b: Int) = a + b
    def concatenate(s1: String, s2: String) = s1 + s2

    메소드를 사용하는 방법은 아래와 같다.

    val x = sum(1, 2)
    val y = concatenate("foo", "bar")

     

    Traits

    Traits 은 스칼라의 아주 재미난 기능인데, 작고 모듈화된 코드 작성에 많은 도움을 준다.

    trait Speaker {
        def speak(): String // body가 없는 abstract 메소드임
    }
    
    trait TailWagger {
        def startTail(): Unit = println("tail is wagging")
        def stopTail(): Unit = println("tail is stopped")
    }
    
    trait Runner {
        def startRunning(): Unit = println("I`m running")
        def stopRunning(): Unit = println("Stopped running")
    }

    위의 trait들을 활용해서 Dog 클래스를 작성할 수 있다.

    class Dog(name: String) extends Speaker with TailWagger with Runner {
        def speak(): String = "Woof!"
    }

    아래는 Cat 클래스이다.

    class Cat extends Speaker with TailWagger with Runner {
        def speak(): String = "Meow"
        override def startRunning(): Unit = println("Yeah ... I don`t run") // override
        override def stopRunning(): Unit = println("No need to stop") // override
    }

     

    Collections classes

    만약 자바를 쓰던 사람이라면 자바의 collection을 스칼라에서 그대로 사용하고 싶을지도 모른다. 실제로 자바 컬렉션을 그대로 사용하는것이 가능하기 때문에 어떤 사람들은 스칼라에 익숙해질때까지 자바 컬렉션을 그대로 사용하기도한다. 하지만 가능한 빨리 스칼라가 기본으로 제공하는 List, ListBuffer, Vector, ArrayBuffer, Map, Set을 사용하는 것이 좋다. 스칼라의 컬렉션은 기본적으로 아주 강력한 기능을 제공하기 때문에 간결한 코드를 작성하기에 아주 좋다.

    Populating lists

    프로그램을 짜다 보면 종종 리스트가 필요할 때가 있다. 아래처럼 여러가지 문법으로 리스트를 만들 수 있다.

    val nums = List.range(0, 10)
    val nums = (1 to 10 by 2).toList
    val letters = ('a' to 'f').toList
    val letters = ('a' to 'f' by 2).toList

    Sequence methods

    스칼라에는 Array, ArrayBuffer, Vector, List와 같은 sequential collections가 있다. List를 사용하여 foreach와 filter 메소드를 활용한 예이다.

    val nums = (1 to 10).toList
    val names = List("joel", "ed", "chris", "maurice")
    
    names.foreach(println)
    //결과
    //
    joel
    ed
    chris
    maurice
    //
    
    nums.filter(_ < 4).foreach(println)
    1
    2
    3

    map을 사용하여 아래와 같은 코드를 만들 수도 있다.

    scala > val doubles = nums.map(_ * 2)
    doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
    
    scala > val capNames = names.map(_.capitalize)
    capNames: List[String] = List(Joel, Ed, Chris, Maurice)
    
    scala > val lessThanFive = nums.map(_ < 5)
    lessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false)

    굳이 설명이 없어도 map이 어떤 기능을 하는지는 이해할 수 있을 것이다. collection의 모든 원소에 map 안에 쓰인 알고리즘을 적용하여 결과물로 이루어진 새로운 collection을 만든다.

    아래는 foldLeft라는 collection의 메소드중 가장 강력한 기능중 하나이다.

    scala > nums.foldLeft(0)(_ + _)
    res0: Int = 55
    
    scala> nums.foldLeft(1)(_ * _)
    res1: Int = 3628800

    foldLeft는 seed value를 받고 seed value부터 시작해 sequential collection의 원소를 사용해 오른쪽에 주어진 연산을 수행한다. 보다시피 위의 foldLeft는 0부터 10까지의 합, 아래는 1부터 10까지의 곱의 결과다. 이외에도 정말 많은 collection class에서 활용할 수 있는 메소드들이 있다.

     

    Tuples

    Tuple을 사용하면 서로 타입이 다른 컬렉션을 하나의 컨테이너로 관리할 수 있다. 튜플은 2개부터 22개의 값을 가질 수 있고 모든 원소가 다른 타입일 수 있다. 예를들어 Int, Double, String의 서로 다른 타입을 갖는 collection이다.

    (11, 11.0, "Eleven") // 3개의 원소를 갖기 때문에 Tuple3라고도 한다.

    튜플은 많은 경우에 편리하게 활용될 수 있다. 예를들면 다른 언어에서 ad hoc class로 구현했던 내용을 튜플로 구현할 수도 있다.

    def getAaplInfo(): (String, BigDecimal, Long) = {
        ("AAPL", BigDecimal(123.45), 101202303L)
    }
    
    val t = getAaplInfo()

    튜플 안의 원소에 접근하고 싶다면 아래와 같이 언더바와 숫자로 접근할 수 있다.

    t._1
    t._2
    t._3

    튜플의 값은 패턴 매칭으로 추출할 수도 있다. 아래 예에서 symbol, price, volumn이라는 이름의 변수에 getAaplInfo()의 값을 바로 할당하는 예제이다.

    scala> val (symbol, price, volume) = getAaplInfo()
    val symbol: String = AAPL
    val price: BigDecimal = 123.45
    val volume: Long = 101202303
    
    scala> symbol
    val res18: String = AAPL
    
    scala> price
    val res19: BigDecimal = 123.45
    
    scala> volume
    val res20: Long = 101202303

    튜플은 여러개를 묶어 활용할 필요가 있는 경우에 유용하며, 만약 같은 원소를 가진 튜플을 여러번 사용해야 할 경우가 있으면 case class를 선언하여 활용할 수 있다.

    case class StockInfo(symbol: String, price: BigDecimal, volume: Long)

     

    지금까지 얘기하지 않은것

    지금까지는 스칼라 입문을 위한 간단한 내용이었다. 아직 많은 내용이 더있는데 이를테면

    • String, 내장 숫자 타입
    • Packaging과 import
    • 자바 컬렉션을 스칼라에서 쓰는 법
    • 자바 라이브러리를 스칼라에서 쓰는 법
    • 스칼라 프로젝트 빌드 방법
    • 스칼라에서 unit testing을 하는 법
    • 스칼라 쉘 스크립트를 짜는 법
    • Map, Set, 다른 컬렉션 클래스들
    • OOP
    • Functional programming
    • 동시성 (Future)
    • 기타 등등...

    '스칼라' 카테고리의 다른 글

    [스칼라] 함수와 클로저  (0) 2022.01.31
    [스칼라] 내장 제어 구문  (0) 2022.01.31
    [스칼라] 함수형 객체  (0) 2022.01.30
    [스칼라] 기본 타입과 연산  (0) 2022.01.30
    [스칼라] 클래스와 객체  (0) 2022.01.30

    댓글

Designed by Tistory.