349
7
장
for 내장
➐ 필요 없는 공백을 제거한 결과 키
-
값 쌍을 산출한다.
Array
[(
String
,
String
)]
이 반환되었다.
Array
인 이유는 제너레이터가
Array
를 반환하는
String
.
split
을 호출했기 때문이다.
“스칼라 언어 명세”(
http
://
bit
.
ly
/
1wNBOR8
)의
6
.
19
절에서
for
내장과 변환 방법에 대한
더 자세한 내용을 보라.
7.4
Option
과 다른 컨테이너 타입
우리는 예제에서
List
,
Array
,
Map
을 사용했다. 하지만
foreach
,
map
,
flatMap
,
withFilter
를 구현해둔 타입이라면 꼭 컬렉션 타입이 아니더라도
for
내장에서 사용할 수 있다. 다시 말해,
컨테이너로 간주할 수 있는 타입이 이런 메서드를 지원한다면 해당 타입의 인스턴스를
for
내
장에서 사용할 수 있다.
다른 몇 가지 컨테이너 타입을 살펴보자.
for
내장이 여러분 코드를 예기치 않은 모습으로 바꿀
수 있음을 보게 될 것이다.
7.4.1
컨테이너로서의
Option
Option
(
http
://
bit
.
ly
/
16yQhkp
)은 이진 컨테이너다. 즉, 어떤 원소를 포함하고 있거나, 그
렇지 않거나 둘 중 하나다.
Option
도 우리에게 필요한
4
가지 메서드를 모두 제공한다.
다음은
Option
에서 어떻게
4
가지 메서드를 정의했는지 보여준다 (스칼라
2
.
11
라이브러리 소
스 코드에서 가져왔다. 설명을 위해 일부 자세한 사항을 생략 또는 변경했다).
sealed abstract class Option
[+
A
]
{
self
=
>
//
➊
...
def isEmpty
:
Boolean
//
Some
이나
None
이
구현한다
.
final def foreach
[
U
](
f
:
A
=
>
U
):
Unit
=
if
(!
isEmpty
)
f
(
this
.
get
)
350
2
부
기본기 다지기
final def map
[
B
](
f
:
A
=
>
B
):
Option
[
B
]
=
if
(
isEmpty
)
None else Some
(
f
(
this
.
get
))
final def flatMap
[
B
](
f
:
A
=
>
Option
[
B
]):
Option
[
B
]
=
if
(
isEmpty
)
None else f
(
this
.
get
)
final def filter
(
p
:
A
=
>
Boolean
):
Option
[
A
]
=
if
(
isEmpty
||
p
(
this
.
get
))
this else None
final def withFilter
(
p
:
A
=
>
Boolean
):
WithFilter
=
new WithFilter
(
p
)
/*
전체
WithFilter
클래스가
‘
새로운
컬렉션을
만들지
않는다
’
라는
계약을
*
준수해야
한다.
물론
기껏해야
원소가
1
개밖에
없는
컬렉션에서는
*
그것이
큰
문제는
아닐
것이다
.
*/
class WithFilter
(
p
:
A
=
>
Boolean
)
{
def map
[
B
](
f
:
A
=
>
B
):
Option
[
B
]
=
self filter p map f
//
➋
def flatMap
[
B
](
f
:
A
=
>
Option
[
B
]):
Option
[
B
]
=
self filter p flatMap f
def foreach
[
U
](
f
:
A
=
>
U
):
Unit
=
self filter p foreach f
def withFilter
(
q
:
A
=
>
Boolean
):
WithFilter
=
new WithFilter
(
x
=
>
p
(
x
)
&&
q
(
x
))
}
}
➊
self
=
>
식은
Option
의 인스턴스가 사용할
this
에 대한 별명을 지정한다. 나중에
WithFilter
안에서 이를 활용한다. 더 자세한 사항은
14
.
6
절 ‘자기 타입 표기’를 참조하라.
➋ 위에서 정의한
self
참조를 사용해서
WithFilter
가 아니라 그 클래스를 둘러싼
Option
인
스턴스에 대해 연산을 수행한다. 만약 여기서
this
를 사용하면
WithFilter
인스턴스를 가리키
게 될 것이다.
final
키워드는 서브클래스가 이 구현을 오버라이딩하지 못하도록 막는다. 기반 클래스에서 파
생 클래스의 존재를 언급한다는 것이 조금 놀라울 것이다. 일반적으로 기반 타입이 자신의 파생
타입의 존재나 그 내용에 대해 안다면 나쁜 객체지향 설계로 간주될 것이다.
하지만
2
장의 기억을 되살려보면,
sealed
키워드는 서브클래스가 같은 파일 안에서만 정의될
수 있다는 것을 의미한다.
Option
은 비어 있거나 (
None
) 그렇지 않거나 (
Some
), 둘 중 하나다.
따라서 이 코드는 튼튼하며 이해하기 쉽고(모든 경우를 다 처리한다), 간결하며, 완전히 합리
적이다.
351
7
장
for 내장
이
Option
메서드에서 중요한 특징은
Option
이 비어 있지 않은 경우에만 함수 인자를 적용한다
는 점이다.
이런 특징으로 인해 아주 흔한 설계 문제를 우아하게 해결할 수 있다. 분산 계산에서 일반적인 패
턴은 계산을 더 작은 작업으로 나누고, 각 작업을 클러스터에 배분한 다음, 결과를 한데 모으는
것이다. 예를 들어 하둡
Hadoop
의 맵리듀스
MapReduce
프레임워크(
http
://
hadoop
.
apache
.
org
)
도 이런 방식을 사용한다. 우리는 돌려받은 결과 중 비어 있는 것은 무시하고, 비어 있지 않은
결과만 처리하고 싶다. 현재로선 작업에서 발생한 오류는 무시한다.
먼저 각 작업이
Option
을 반환한다고 가정하자.
None
은 결과가 없는 경우에 반환되며,
Some
은
결과가 있는 경우 그 결과를 포장해서 반환된다. 우리는 가장 우아한 방식으로
None
을 걸러서
없애고 싶다.
다음 예를 보자.
3
개의 결과를 리스트에 넣었다. 각각은
Option
[
Int
]
타입이다.
//
src
/
main
/
scala
/
progscala2
/
forcomps
/
for
-
options
-
seq
.
sc
val results
:
Seq
[
Option
[
Int
]]
=
Vector
(
Some
(
10
),
None
,
Some
(
20
))
val results2
=
for
{
Some
(
i
)
<
-
results
}
yield
(
2
*
i
)
//
반환
:
Seq
[
Int
]
=
Vector
(
20
,
40
)
Some
(
i
)
<
-
list
패턴은
results
의 원소와 매치되면서,
None
값을 제거하고
Some
값 안의 정
수를 뽑아낸다. 그 후 우리가 원하는 마지막 식을 산출한다. 출력은
Vector
(
20
,
40
)
이다.
연습 삼아 변환 규칙을 차례로 적용해보자. 다음은 첫 번째 규칙을 적용해서 각각의
pat
<
-
expr
식을
withFilter
식으로 바꾼 것이다.
//
변환
단계
#
1
val results2b
=
for
{
Some
(
i
)
<
-
results withFilter
{
case Some
(
i
)
=
>
true
case None
=
>
false
}
Get 프로그래밍 스칼라: 실용적인 스칼라 활용법을 익히는 가장 확실한 실전 바이블 (2.11.x 버전 기반) now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.