-
Notifications
You must be signed in to change notification settings - Fork 36
6장 functional objects
JeongHoon Byun (aka Outsider) edited this page Jun 9, 2013
·
6 revisions
- 이번 장에서는 어떤 상태도 갖지 않는 펑셔널 객체를 정의하는 클래스를 설명한다.
- Rarional 클래스에 대한 스펙 설명
- 분수 얘기임.
- 최종적으로 다음과 같이 되어야 함.
scala> val oneHalf = new rational(1, 2)
oneHalf: Rational = 1/2
scala> val twoThirds = new rational(2, 3)
towThirds: Rational = 2/3
scala> (oneHalf / 7) + (1 - twoThirds)
res0: Rational = 17/42
class Rational(n: Int, d: Int)
- class 선언 시 클래스명 뒤에 () 로 class parameter를 명시할 수 있음.
- 스칼라 컴파일러가 이 parameter를 받는 primary constructor를 만든다.
- class 내부의 코드 중 field나 method 선언에 해당하지 않는 모든 코드는 primary constructor에 속함.
Immutable object trade-offs
- 장점
- 뮤터블보다 쉽다.(변경되는 복잡한 상태가 없다.)
- 변결될 걱정없이 전달할 수 있다.
- 상태를 변경할 수 없으므로 동시성 이슈가 없다.
- 안전한 해쉬테이블 키를 만들 수 있다.
- 단점
- 객체를 수정해야 하는 경우 큰 객체를 복사하는 비용이 든다.
- 보기 편하게
toString
을 오버라이드한다.
class Rational(n: Int, d: Int) {
override def toString = n + "/" + d
}
scala> val x = new Rational(1, 3)
x: Rational = 1/3
- 분모는
0
이 될 수 없지만 현재는 가능하다.
scala> new Rational(5, 0)
res1: Rational = 5/0
- 메서드나 생성자에 전달한 값의 제약사항을 검사해야 하는데 한자기 방법이
require
이다.
class Rational(n: Int, d: Int) {
require(d != 0)
override def toString = n + "/" + d
}
-
require
메서드는 불리언 파라미터를 받는다. 파라미터가true
이면 넘어가고false
이면IllegalArgumentException
을 던진다.
-
Rational
의 덧셈을 위해서add
함수가 필요하다. immutable이므로add
함수는 새로운Rational
객체를 반환한다.
class Rational(n: Int, d: Int) {
require(d != 0)
override def toString = n + "/" + d
def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d)
}
- 이 코드는
val d is not a member of Rational
이라는 오류가 발생한다. - class parameter는 객체 내에서만 사용할 수 있으므로
that
의n
과d
에 접근하려면 필드로 만들어 주어야 한다.
class Rational(n: Int, d: Int) {
require(d != 0)
val numer: Int = n
val denom: Int = d
override def toString = numer + "/" + denom
def add(that: Rational): Rational = new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
}
scala> val oneHalf = new rational(1, 2)
oneHalf: Rational = 1/2
scala> val twoThirds = new rational(2, 3)
towThirds: Rational = 2/3
scala> oneHalf add twoThirds
res3: Rational = 7/6
- 10.6절에 소개될 parametric fields를 이용하면 귀찮게 생성자와 별도로
val
/var
를 선언할 필요 없음.class Rational(n: Int, d: Int)
->class Rational(val n: Int, val d: Int)
-
this
로 자기 자신을 참조할 수 있음.
def lessThan(that: Rational) =
this.numer * that.denom < that.numer * this.denom
- primary constructor가 아닌 생성자는 auxiliary constructor라고 부른다.
- 예를 들어 Rational을
5/1
로 작성하는 대신5
로 작성할 수 있어야 한다.(ex:new Rational(5)
) - auxiliary constructor는
def this(...)
로 시작한다.
class Rational(n: Int, d: Int) {
require(d != 0)
val numer: Int = n
val denom: Int = d
def this(n: Int) = this(n, 1) // auxiliary constructor
override def toString = numer + "/" + denom
def add(that: Rational): Rational = new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
}
scala> val y = new Rational(3)
y: Rational = 3/1
- auxiliary constructor의 시작은 항상
this(...)
형태로 다른 생성자를 호출해야 하고 최종적으로는 primary constructor를 호출해야 한다. - 혹시나 primary constructor를 호출하지 않는 경우가 있을까 해서 잠깐 실험을 해 보니 compile 오류가 발생함.
class A( a: Int ) {
def this( a: Double) = this(a,1)
def this( a: Double, n: Int ) = this(a)
}
<console>:6: error: called constructor's definition must precede calling constructor's definition
def this(n: Double) = this(n,1)
- 즉, 생성자가 호출할 다른 생성자는 자신보다 코드 상에서 앞에 선언되어야 함. 이를 보면 반드시 primary constructor가 실행되리라고 유출할 수 있음.
- 오직 primary 생성자만 superclass 생성자를 호출할 수 있다.
- 현재 Rational은 66/42를 11/7로 바꿔주지 않는다. 그래서 분자와 분모를 최대공약수로 나누어 주어야 한다.
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer: Int = n / g
val denom: Int = d / g
def this(n: Int) = this(n, 1)
override def toString = numer + "/" + denom
def add(that: Rational): Rational = new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
private def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
}
scala> new Rational(66, 42)
res7: Rational = 11/7
- 더 편리하게 사용하도록
x + y
형태나 실수인 경우에는x.add(y)
나x add y
같은 형태로 작성할 수 있다.
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer: Int = n / g
val denom: Int = d / g1
def this(n: Int) = this(n, 1)
def + (that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
def * (that: Rational): Rational =
new Rational(numer * that.nemer, denom * that.denom)
override def toString = numer + "/" + denom
private def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
}
scala> val x = new Rational(1, 2)
x: Rational = 1/2
scala> val y = new Rational(2, 3)
y: Rational = 2/3
scala> x + y
res8: Rational = 7/6
- 5.8장에서 설명한 연산자 우선순위에 따라
*
연산자가+
보다 우선한다.
scala> x + x * y
res10: Rational = 5/6
scala> (x + x) * y
res11: Rational = 2/3
scala> x + (x * y)
res12: Rational = 5/6
- scala의 identifier는 alphanumeric과 operator로 나뉨
- alphanumeric identifier : letter, underscore로 시작함. $도 사용 가능하지만, scala 내부 구현과 혼동을 일으키기 때문에 쓰면 안됨!
- naming convention은 자바와 동일(camel case). 단, 상수 정의 시 자바에선
X_OFFESET
과 같이 표현했으나 scala는XOffset
과 같이 상수에도 camel case를 사용함. - 상수는 val과는 다르다. val은 변수다
- operator identifier: 하나 이상의 opertaor 문자(
+
,:
,?
,~
,#
)로 구성됨. - mixed identifier:
alphanumeric identifier
+_
+operator identifier
. ex. unary_+, myvar_= (요 형태는 18장의 properties에서 응용한다고 함) - literal identifier: back tick(`) 사이에 표기한 string. ex.
\
x`,
`yeild``. java에서 scala reserved word등을 사용할 때.
- 현재까지 작선한 Ratioanal에서는 정수와 섞어서 사용할 수 없다. 즉,
r * 2
처럼 작성할 수 없고r * new Rational(2)
로 작성해야 한다. - 이를 위해서 산술연산 메서드는 2가지 버전이 필요하다.(Rational을 받는 메서드와 정수를 받는 메서드) 즉, 오버로드해야 한다.
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer: Int = n / g
val denom: Int = d / g1
def this(n: Int) = this(n, 1)
def + (that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
def + (i: Int): Rational = new Rational(numer + i * denom, denom)
def - (that: Rational): Rational =
new Rational(
numer * that.denom - that.numer * denom,
denom * that.denom
)
def - (i: Int): Rational = new Rational(numer - i * denom, denom)
def * (that: Rational): Rational =
new Rational(numer * that.nemer, denom * that.denom)
def * (i: Int): Rational = new Rational(numer * i, denom)
def / (that: Rational): Rational =
new Rational(numer * that.denom, denom * that.nemer)
def / (i: Int): Rational = new Rational(numer, denom * i)
override def toString = numer + "/" + denom
private def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
}
- 이제
r * 2
로 작성할 수 있지만2 * r
로는 작성할 수 없다. 이는2.*(r)
에서 정수에 Rational을 인자로 받는*
연산자가 없기 때문이다. - 이를 위해서 정수를 Rational로 자동변환하는 implicit conversion을 만들 수 있다.
scala> implicit def intToRational(x: Int) = new Rational(x)
scala> val r = new Rational(2, 3)
r: Rational = 2/3
scala> 2 * r
res16: Rational = 4/3
- implicit conversion, operator 재정의는 코드를 간결하게 하는 데 유용하긴 하지만, 남들이 코드를 읽을 때 혼동의 여지를 주기도 하므로 주의해서 사용해야 함.