-
[스칼라] 함수형 객체스칼라 2022. 1. 30. 15:46
class ComplexNumber(real: Double, imaginary: Double) { require(imaginary != 0) override def toString = s"$real + ${imaginary}i" }
Programming in scala 4th edition 을 읽고 정리한 글입니다.
1. 함수형 객체: 변경 가능한 상태를 전혀 갖지 않는 객체
책에서는 분수를 가지고 설명을 하므로 복소수를 가지고 구현해보도록 한다.
2. ComplexNumber 클래스 생성
class ComplexNumber(real: Double, imaginary: Double)
스칼라는 클래스에서 사용할 파라미터를 클래스 선언과 함께 바로 정의할 수 있다. 이것을 클래스 파라미터라고 하고 스칼라 내부적으로 클래스 파라미터와 같은 인자를 받는 주 생성자(primary constructor)를 만든다.
스칼라는 필드나 메서드에 속하지 않은 코드들을 주 생성자로 밀어넣는다. 아래 예시에서 확인할 수 있다.
// ComplexNumber.sala class ComplexNumber(real: Double, imaginary: Double) { println(s"This is Complex Number. real: $real, imaginary: $imaginary") } // ComplexNumber 클래스로 객체를 만드는 프로그램 object ComplexExample extends App { val complexNumber = new ComplexNumber(10, 10) } // 위 코드를 실행하면 아래의 출력결과를 얻는다 // 출력결과: This is Complex Number. real: 10.0, imaginary: 10.0
3. ComplexNumber의 toString 구현
스칼라의 toString은 java.lang.Object의 toString구현을 물려받는데 toString은 기본적으로 객체의 클래스이름과@id를 출력해준다. toString을 오버라이드하여 복소수 표현을 출력하도록 변경한다. 스칼라에서 override는 함수 선언 앞에 override 키워드를 붙여주면 된다.
// ComplexNumber.scala class ComplexNumber(real: Double, imaginary: Double) { override def toString = s"$real + ${imaginary}i" } // ComplexExample.scala object ComplexExample extends App { val complexNumber = new ComplexNumber(1, 2) print(complexNumber) } // ComplexExample을 실행시키면 출력 결과: 1.0 + 2.0i
4. 선결 조건 확인
예시를 좀 더 간단하게 만들기 위해서 허수부가 존재해야만한다고 가정하자. 주 생성자 내부에 선결조건(precondition)을 정의할 수 있다. 선결조건을 정의하면 메서드나 생성자가 전달받은 값을 제한할 수 있다. 여기에서는 require문을 사용해서 선결조건을 지정한다.
class ComplexNumber(real: Double, imaginary: Double) { require(imaginary != 0) override def toString = s"$real + ${imaginary}i" }
이제 ComplexNumber로 객체럴 생성할때 imaginary 가 0이면 IllegalArgumentException이 발생하게 된다.
5. 필드 추가
두 복소수를 더하는 add 함수를 작성해본다.
class ComplexNumber(real: Double, imaginary: Double) { require(imaginary != 0) override def toString = s"$real + ${imaginary}i" def add(other: ComplexNumber): ComplexNumber = { new ComplexNumber(real + other.real, imaginary + other.imaginary) } }
위와 같이 작성하고 실행하면 add 함수 안에서 other 의 real, imaginary를 바로 호출할 수 없어 에러가 발생한다. 필드를 추가해서 클래스파라미터를 할당해 주는 방법으로 구현해본다.
class ComplexNumber(r: Double, i: Double) { require(i != 0) val real = r val imaginary = i override def toString = s"$real + ${imaginary}i" def add(other: ComplexNumber): ComplexNumber = { new ComplexNumber(real + other.real, imaginary + other.imaginary) } }
6. 자기참조
메서드가 자신의 객체를 참조할때는 this 키워드를 사용한다. 예를들어 위의 add함수에서 자신의 객체의 real을 참조하고 싶으면 this.real로 사용할 수 있다. (위와 같은 경우는 생략 가능하다.) 변수명이 중복되는 등의 경우에는 this를 명시해서 메서드의 호출 대상 인스턴스참조임을 표현해야한다.
7. 보조 생성자
주 생성자외에 여러가지 생성자가 필요한 경우 보조 생성자 (auxiliary constructor)를 만들 수 있다. 예를 들어서 허수부만 존재하는 복소수를 위한 생성자를 아래와 같이 만들어둘 수 있다.
class ComplexNumber(r: Double, i: Double) { require(i != 0) val real = r val imaginary = i override def toString = s"$real + ${imaginary}i" def this(i: Double) = this(0, i) def add(other: ComplexNumber): ComplexNumber = { new ComplexNumber(real + other.real, imaginary + other.imaginary) } }
스칼라에서 모든 보조생성자는 반드시 같은 클래스 내의 다른 생성자를 호출하는 코드로 시작해야한다. 꼭 주 생성자를 호출할 필요는 없지만 그보다 앞에 있는 생성자를 반드시 호출해야되기 때문에 결국 주 생성자가 호출된다.
8. 연산자 정의
이제 복소수의 덧셈과 곱셈을 정의해본다.
//ComplexNumber.scala class ComplexNumber(r: Double, i: Double) { require(i != 0) val real = r val imaginary = i override def toString = s"$real + ${imaginary}i" def this(i: Double) = this(0, i) def add(other: ComplexNumber): ComplexNumber = { new ComplexNumber(real + other.real, imaginary + other.imaginary) } def +(other: ComplexNumber): ComplexNumber = { add(other) } def *(other:ComplexNumber): ComplexNumber = { val real = this.real * other.real - this.imaginary * other.imaginary val imaginary = this.real * other.imaginary + this.imaginary * other.real new ComplexNumber(real, imaginary) } }
//ComplexExample.scala object ComplexExample extends App { val complexNumberA = new ComplexNumber(1, 2) val complexNumberB = new ComplexNumber(3 ,4) println(complexNumberA + complexNumberB) println(complexNumberA * complexNumberB) } // 실행 결과 4.0 + 6.0i -5.0 + 10.0i
9. 오버로드
곱셈 구현에서 단순 배수를 하는 구현을 추가한다. 이렇게 이름은 같지만 파라미터가 다른 메서드를 정의하는 것을 메서드 오버로드라고 한다.
class ComplexNumber(r: Double, i: Double) { require(i != 0) val real = r val imaginary = i override def toString = s"$real + ${imaginary}i" def this(i: Double) = this(0, i) def add(other: ComplexNumber): ComplexNumber = { new ComplexNumber(real + other.real, imaginary + other.imaginary) } def + (other: ComplexNumber): ComplexNumber = { add(other) } def * (other:ComplexNumber): ComplexNumber = { val real = this.real * other.real - this.imaginary * other.imaginary val imaginary = this.real * other.imaginary + this.imaginary * other.real new ComplexNumber(real, imaginary) } def * (r: Int): ComplexNumber = { new ComplexNumber(real * r, imaginary * r) } }
10. 암시적 타입 변환
이제 아래의 코드들을 모두 동작시킬수 있다.
object ComplexExample extends App { val complexNumberA = new ComplexNumber(1, 2) val complexNumberB = new ComplexNumber(3 ,4) println(complexNumberA + complexNumberB) println(complexNumberA * complexNumberB) println(complexNumberB * 2) } // 결과 4.0 + 6.0i -5.0 + 10.0i 6.0 + 8.0i
만약 complexNumberB * 2 대신에 2 * complexNumberB라고 표현하면 어떨까? 앞서 말했다시피 2.*(complexNumberB)로 변환되는데 Int 의 * 메서드에는 ComplexNumber를 파라미터로 받는 오버로드가 없기 때문에 에러가난다. 이를 해결하기 위해서는 암시적 타입변환을 위한 메서드를 생성해주면 된다.
//ComplexNumber.scala class ComplexNumber(r: Double, i: Double) { // require(i != 0) 정수를 변환할 수 있도록 하기위해 이 제약조건은 제거하였다. val real = r val imaginary = i override def toString = s"$real + ${imaginary}i" def this(i: Double) = this(0, i) def add(other: ComplexNumber): ComplexNumber = { new ComplexNumber(real + other.real, imaginary + other.imaginary) } def + (other: ComplexNumber): ComplexNumber = { add(other) } def * (other:ComplexNumber): ComplexNumber = { val real = this.real * other.real - this.imaginary * other.imaginary val imaginary = this.real * other.imaginary + this.imaginary * other.real new ComplexNumber(real, imaginary) } def * (r: Int): ComplexNumber = { new ComplexNumber(real * r, imaginary * r) } }
object ComplexExample extends App { val complexNumberB = new ComplexNumber(3 ,4) implicit def intToComplexNumber(x: Int) = new ComplexNumber(2, 0) println(2 * complexNumberB) } // 출력 결과: 6.0 + 8.0i
마무리
이번 장에서는 클래스 파라미터, 생성자, 연산자를 메서드로 정의하는법, 오버라이딩, 오버로딩에 대해서 알아보았다.
'스칼라' 카테고리의 다른 글
[스칼라] 함수와 클로저 (0) 2022.01.31 [스칼라] 내장 제어 구문 (0) 2022.01.31 [스칼라] 기본 타입과 연산 (0) 2022.01.30 [스칼라] 클래스와 객체 (0) 2022.01.30 [스칼라 입문] PRELUDE꞉ A TASTE OF SCALA 번역 (0) 2022.01.09