[정리] Scala 정리(1) - 기초

6 분 소요

스칼라 학교!을 개인적으로 정리한 것입니다.

인터프리터 시작하기

$ sbt console

값과 변수

val two = 1 + 1 // value, cannot be modified
var two = 1 + 1 // variable, can be modified

함수

/* functions */
def sum(x:Int, y:Int): Int = x + y
def print_hello() = println("Hello World")
/* lambda functions */
(x:Int) => x * x

이름 없는 함수를 다른 함수나 식에 넘기거나 val에 저장할 수도 있다.

부분적용(Partial application)

{ _ + 2 } 이라는 문맥에서 밑줄은 이름이 없는 매개변수를 가리킨다.

def adder(m: Int, n: Int) = m + n
// adder: (m: Int,n: Int)Int
val add2 = adder(2, _:Int)
// add2: (Int) => Int = <function1>
add2(3)
// res50: Int = 5

Curried Function

떄로 함수의 인자중 일부를 적용하고, 나머지는 나중에 적용하게 남겨두는 것이 더 쓸모있는 경우가 있다.

scala> def multiply(m: Int)(n: Int): Int = m * n
// multiply: (m: Int)(n: Int)Int

/* 물론 두 인자를 한꺼번에 적용할 수도 있다.*/
scala> multiply(2)(3)
// res0: Int = 6

언더스코어를 이용해본다.

scala> val timesTwo = multiply(2) _
// timesTwo: (Int) => Int = <function1>

scala> timesTwo(3)
// res1: Int = 6

scala> val multiply2 = multiply(_:Int)(_:Int)
scala> multiply2(3,5) // returns 15

가변길이 인자

동일한 타입의 매개변수가 반복되는 경우

// sum function
def sum(args: Float*): Float ={
    args.reduce { 
        (m:Float, n:Float) => m+n
    }
}
sum(1,2,3,4,5) // returns 15.0

클래스

/* Class Defining */
class Calculator {
    val brand: String = "HP"
    def sum(m:Int, n:Int):Int = m+n
}

/* instantiate */
val calc = new Calculator

/* call class method */
calc.sum(1,2)

/* access class variable */
calc.brand

Constructor

특별히 생성자 문법을 제공하지는 않으며 단순히 초기화 파라미터만 받는다. 인스턴스 메소드 외의 모든 부분이 생성자가 된다.

// (brand: String) is parameter of constructor
class Calculator(brand: String) {
    // constructor
    val color: String = if (brand == "TI"){
        "blue"
    } else if (brand=="HP"){
        "black"
    } else{
        "white"
    }
    // instance method
    def sum(m:Int, n:Int):Int = m+n
}

scala> val calc = new Calculator("HP")
// calc: Calculator = Calculator@1e64cc4d

scala> calc.color
// res0: String = black

위 예제 클래스에서 클래스가 인스턴스화되면 초기화 변수인 brand에 따라서 color의 value가 정해지는 것을 볼 수 있다.

함수 vs 메소드
아래 예시를 참고해보자 ```scala class C { var acc = 0 def minc = { acc += 1 } val finc = { () => acc += 1 } }

scala> val c = new C // c: C = C@1af1bd6

scala> c.minc // c.minc() 호출함

scala> c.finc // 함수 값(반환값이 아니라 함수 자체)을 반환함 res2: () => Unit =


### 상속
```scala
class ScientificCalculator(brand: String) extends Calculator(brand) {
  def log(m: Double, base: Double) = math.log(m) / math.log(base)
}

오버라이딩

class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
  def log(m: Int): Double = log(m, math.exp(1))
}

abstract class

abstract class Shape{
    def getArea():Int
}

class Circle(r: Int) extends Shape {
    def getArea():Int = {r * r * 3}
}

val s = new Shape
// <console>:8: error: class Shape is abstract; cannot be instantiated
//        val s = new Shape
               ^

val c = new Circle(2)
// c: Circle = Circle@65c0035b

Traits

트레잇(trait)은 다른 클래스가 확장(즉, 상속)하거나 섞어 넣을 수 있는(이를 믹스인Mix in 이라 한다) 필드와 동작의 모음이다.

abstract class Car{
    val brand: String
}

trait Shiny{
    val shineRefraction: Int
}

class BMW extends Car with Shiny{
    val brand="BMW"
    val shineRefration=12
}

Traits vs. Abstract Class

  • 공통점: 이를 상속(또는 확장)하는 쪽에서 구현을 해야 한다.
  • 차이점:
    • 클래스의 다중상속은 안된다. 다중상속이 필요하면 트레잇을 사용해야 한다.
    • 생성자의 매개변수가 필요한 경우 추상클래스를 사용해야 한다. 트레잇의 매개변수는 허용되지 않는다.

타입

제네릭 함수를 만들 떄는 제네릭을 만들 변수를 각괄호([], squared bracket)으로 감싼다.

trait Cache[K, V]{
    def get(key: K): V
    def put(key: K, value: V)
    def delete(key: K)
}

def remove[K](key: K) // 메소드에도 타입매개변수를 추가할 수 있다.

apply 메소드

apply 메소드를 사용하면 클래스나 객체의 용도가 주로 하나만 있는 경우에 유용하다.

scala> class Foo {}
// defined class Foo

scala> object FooMaker {
     |   def apply() = new Foo
     | }
// defined module FooMaker

scala> val newFoo = FooMaker()
// newFoo: Foo = Foo@5b83f762

scala> class Bar {
     |   def apply() = 0
     | }
// defined class Bar

scala> val bar = new Bar
// bar: Bar = Bar@47711479

scala> bar()
// res8: Int = 0

apply를 정의하면 메소드를 호출하듯 객체 인스턴스를 호출할 수 있다. 객체 인스턴스를 호출하면 그 객체(클래스)에 정의된 apply()가 호출된다.

객체(object)

object키워드로 선언된 객체를 말한다. 클래스와 객체가 같은 이름을 가질 수도 있다. 클래스의 유일한 인스턴스를 넣기 위해 사용한다.

object Timer{
    var count = 0
    def currentCount(): Long = {
        count += 1
        count
    }
}

scala> Timer.currentCount
// res6: Long = 1

함수는 객체이다.

또는 트레이트의 집합이다.

인자를 하나만 받는 함수 = Function1의 인스턴스, 인자를 두 개 받는 함수 = Function2의 인스턴스, …

각 트레잇은 apply()메소드가 정의되어 있다. 그래서 실제로 함수를 호출하는 것은 Function*의 인스턴스의 apply() 메소드를 호출하는 것과 같다.

scala> class AddOne extends Function1[Int, Int] {
     |   def apply(m: Int): Int = m + 1
     | }
// defined class AddOne

scala> val plusOne = new AddOne()
// plusOne: AddOne = <function1>

scala> plusOne(1)
// res0: Int = 2
/** extends Function1[Int, Int]는 extends (Int => Int)라고 
 *  더 알아보기 쉽게 쓸 수 있다. 
 */ 

class AddOne extends (Int => Int) {
  def apply(m: Int): Int = m + 1
}

패키지

package com.twitter.example

이를 파일 맨 앞에 선언하면 해당 파일 내의 모든 것들이 패키지에 포함된다. 값이나 함수는 클래스나 객체 밖에 존재할 수 없다. 객체(자바의 static함수와 동일)를 이용해 정적 함수를 관리할 수 있다.

패턴매칭(스위치 문)

times match {
  case 1 => "one"
  case 2 => "two"
  case _ => "some other number"
}
// using conditional statement 
times match {
  case i if i == 1 => "one"
  case i if i == 2 => "two"
  case _ => "some other number"
}

// withoug _ ==> runtime error

타입 매칭

타입으로도 경우를 나눌 수 있다.

def bigger(o: Any): Any = {
  o match {
    case i: Int if i < 0 => i - 1
    case i: Int => i + 1
    case d: Double if d < 0.0 => d - 0.1
    case d: Double => d + 0.1
    case text: String => text + "s"
  }
}

클래스 멤버 따라 매칭

def calcType(calc: Calculator) = calc match {
  case calc.brand == "HP" && calc.model == "20B" => "financial"
  case calc.brand == "HP" && calc.model == "48G" => "scientific"
  case calc.brand == "HP" && calc.model == "30B" => "business"
  case _ => "unknown"
}

Case Class

case class Calculator(brand: String, model: String)
val hp20b = Calculator("HP", "20b")
val hp20B = Calculator("HP", "20b")
hp20b == hp20B // this operation is generated automatically
// toString 함수도 기본적으로 지원된다.

케이스 클래스로 패턴 매칭하기

val hp20b = Calculator("HP", "20B")
val hp30b = Calculator("HP", "30B")

def calcType(calc: Calculator) = calc match {
  case Calculator("HP", "20B") => "financial"
  case Calculator("HP", "48G") => "scientific"
  case Calculator("HP", "30B") => "business"
  case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}
// alternative for last case
case Calculator(_, _) => "Calculator of unknown type"
case _ => "Calculator of unknown type"

예외

예외 처리시 try-catch-finally 문법에 패천 매칭 사용 가능하다.

try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
} finally {
  remoteCalculatorService.close()
}
// another example
val result: Int = try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => {
    log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
    0
  }
} finally {
  remoteCalculatorService.close()
}

댓글남기기