memostack
article thumbnail
Published 2020. 2. 23. 14:58
Scala 추상 val 초기화 Language/Scala
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.tistory.com/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
반응형

추상 val 초기화시 문제점

아래와 같이 (추상 val을 가진) 트레이트를 인스턴스화하여면, 추상 val의 정의를 구현해야 한다.

trait RationalTrait {
  val numerArg: Int
  val denomArg: Int
  require(denomArg != 0)
}
위 예제(트레이트)는 분수를 표현하는 트레이트이다. (numerArg: 분자, denomArg: 분모) 따라서, denomArg 는 0이 될 수 없다.

만약 위 Rational 이 트레이트(Trait) 가 아닌 클래스였다면, new Rational(expr1, expr2) 와 같이 구현할 수 있다.

new Rational(expr1, expr2) 실행 순서

  1. expr1, expr2 식을 계산한다.
  2. Ratinal()을 초기화 한다.

하지만, 트레이트의 경우는 초기화 순서가 반대이다.

new RatinalTrait(expr1, expr2) 실행 순서

  1. RatinalTrait() 를 초기화 한다.
  2. expr1, expr2를 계산한다.
trait RationalTrait {
  val numerArg: Int
  val denomArg: Int
  require(denomArg != 0)
}

val x: Int = 2

new RationalTrait {
  override val numerArg: Int = 1 * x
  override val denomArg: Int = 2 * x
  // RatinalTrait가 먼저 초기화되면서, denomArg는 default 값인 0을 갖는다.
  // require() 에서 오류가 발생한다.
}

RatinalTrait 가 먼저 초기화되면서, denomArg는 default 값으로 0을 갖게된다.

require(denomArg != 0) 에 의해 오류가 발생한다.

scala> new RationalTrait {
     |   override val numerArg: Int = 1 * x
     |   override val denomArg: Int = 2 * x
     | }
java.lang.IllegalArgumentException: requirement failed
  at scala.Predef$.require(Predef.scala:327)
  at RationalTrait.$init$(<console>:4)
  ... 38 elided

 

추상 val 초기화하는 방법

추상 val을 초기화하는 방법에는 2가지가 있다.

  • 필드를 미리 초기화하기
  • 지연(lazy) val 을 사용하기

필드를 미리 초기화하기

슈퍼 클래스를 호출하기전에 서브클래스에서 필드를 초기화하면 된다.

스칼라에서는 미리 필드 정의를 통해 초기화하는 방법이 있다. 중괄호를 이용하여 클래스 생성자 호출 앞에 위치시키면 된다.

trait RationalTrait {
  val numerArg: Int
  val denomArg: Int
  require(denomArg != 0)
}

val x: Int = 2

new {
  val numerArg = 1 * x
  val denomArg = 1 * x
} with RationalTrait // with 키워드를 사용.

경고가 발생하긴 하지만 오류는 발생하지 않는다. 

익명 클래스를 통해 예제를 소개했지만, 아래와 같이 이름이 있는 서브 클래스에서도 필드를 미리 초기화 할 수 있다.

class RationalClass(n: Int, d: Int) extends {
  val numerArg: Int = 1 * x
  val denomArg: Int = 1 * x
} with RationalTrait {
  // 분수의 합 기능을 구현한 메소드.
  def + (that: RationalClass) = new RationalClass(
  	numer * that.denom + that.numer * denom,
  	denom * that.denom
  )
}

참고로 미리 초기화하는 경우, 슈퍼클래스가 초기화되기전에 먼저 초기화되므로, this를 사용할 수 없다.

this를 사용하면, 컴파일 에러 발생

지연(lazy) val 을 사용하기

lazy val 은 사용하는 시점에 초기화가 된다.

scala> object Demo {
     |   val x = {
     |     println("initializing x")
     |     "done"
     |   }
     | }
defined object Demo

scala> Demo
initializing x
res0: Demo.type = Demo$@71d8cfe7

scala> println(Demo.x)
done

Demo를 초기화하면서 "initializing x" 가 같이 실행된다. (x가 같이 초기화 됨)

 

lazy val 을 사용한 경우...

scala> object Demo2 {
     |   lazy val x = {
     |     println("initializing x")
     |     "done"
     |   }
     | }
defined object Demo2

scala> Demo2
res2: Demo2.type = Demo2$@348137e8

scala> println(Demo2.x)
initializing x
done

Demo2.x 를 호출하는 시점에 초기화되면서, "initializing x" 가 출력된다. (x가 실행하는 시점에 초기화 된다)

실행시점에 초기화 한다고, val 을 결코 두번 계산하지 않는다. 처음 lazy val 을 계산한 값을 저장해두고 있기 때문이다.
그래서, 다시 사용하는 경우 저장해두었던 값을 재사용한다. (def 와의 차이점)

지연 사용하는 값들끼리는 순서가 중요하지 않다. 이는 lazy value의 중요한 특징이다. (단, 부수효과가 없어야 함)

 

부수 효과가 있는(초기화 순서에 문제가 될 수 있다) 명령형 코드의 경우는 lazy value 와는 어울리지 않는다. 반면, 부수효과(side effect) 가 없는 함수형 코드에는 어울린다.

지연 함수 언어
스칼라는 지연 정의와 함수형 코드를 조화시키려는 노력을 기울인 첫 언어가 아니다.
모든 값과 파라미터를 지연 연산하는 지연 함수형 프로그래밍 언어(lazy functional programming language) 라는 분야가 있고, 이런 언어 중 가장 잘 알려진 하스켈(Haskell) 이 있다.
반응형

'Language > Scala' 카테고리의 다른 글

Scala의 열거형(Enumeration type)  (0) 2020.02.23
Scala 추상 타입 (type T)  (0) 2020.02.23
Scala 추상 멤버 (타입멤버, 메소드, val, var)  (0) 2020.02.22
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.tistory.com/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
profile

memostack

@bluemiv_mm

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!