337
3
.
6
절 ‘스칼라
for
내장’에서
for
내장에 대해 설명했다. 지금은
for
내장이 단지 흔히 볼 수 있
는
for
루프를 더 유연하고 좋게 만든 것으로 보일 것이다. 실제로는 겉보기와 다르게 복잡한 내
용이 숨겨져 있다. 하지만 이런 복잡성에는 여러 설계 문제에 대해 우아한 해법을 제시할 수 있는
간결한 코드를 작성할 수 있다는 이점이 있다.
이번 장에서는
for
내장을 정말로 이해하기 위해 표면 아래로 내려가 볼 것이다. 스칼라에서
for
내장을 어떻게 구현하는지와 여러분 자신의 컨테이너를
for
내장에서 활용할 수 있게 만드는
방법을 살펴볼 것이다.
여러 처리 단계로 이루어진 프로그램을 실행하는 과정에서 오류를 처리하는 방법과 같은 몇 가
지 일반적인 설계 문제를, 다양한 스칼라 컨테이너가
for
내장을 사용해서 어떻게 해결하는지
검토하는 것으로 본 장을 마칠 것이다. 또한 자주 발생하는 관용적인 표현을 함수형으로 처리할
수 있는 잘 알려진 기법을 뽑아낼 것이다.
7.1
돌아보기:
for
내장의 기본 요소
for
내장에는 하나 이상의 제너레이터 식이 들어간다. 그리고 선택적으로 가드 식 (
for
걸러내
기)이나 값 정의가 있을 수 있다. 출력은 새로운 컬렉션을 만들거나, 매 단계마다 출력을 표시하
는 등의 부수 효과 블록을 만들기 위해 ‘산출
yield
’될 수 있다. 다음은 이런 기능을 모두 보여주는
예제로, 텍스트 파일에서 빈 줄을 제거하는 프로그램이다.
for
내장
CHAPTER
7
338
2
부
기본기 다지기
//
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
package progscala2
.
forcomps
object RemoveBlanks
{
/**
*
지정한
입력
파일에서
빈
줄을
제거한다
.
*/
def apply
(
path
:
String
,
compressWhiteSpace
:
Boolean
=
false
):
Seq
[
String
]
=
for
{
line
<
-
scala
.
io
.
Source
.
fromFile
(
path
).
getLines
.
toSeq
//
➊
if line
.
matches
("""^\
s
*$""")
==
false
//
➋
line2
=
if
(
compressWhiteSpace
)
line replaceAll
("\\
s
+",
"
")
//
➌
else line
}
yield line2
//
➍
/**
*
지정한
입력
파일에서
빈
줄을
제거하고
남은
줄들을
표준
출력에
하나씩
보낸다
.
*
@
param args
파일
경로
목록이다.
파일
이름의
앞에
‘
-
’
를
붙이면
*
파일에서
연속된
여러
공백을
하나로
압축해준다
.
*/
def main
(
args
:
Array
[
String
])
=
for
{
path2
<
-
args
//
➎
(
compress
,
path
)
=
if
(
path2 startsWith
"
-
")
(
true
,
path2
.
substring
(
1
))
else
(
false
,
path2
)
//
➏
line
<
-
apply
(
path
,
compress
)
}
println
(
line
)
//
➐
}
➊
scala
.
io
.
Source
(
http
://
bit
.
ly
/
1tIcX1h
)를 사용해서 파일을 열고 각 줄을 읽는다.
getLines
는
scala
.
collection
.
Iterator
(
http
://
bit
.
ly
/
1q92kQc
)를 반환한다. 우리는 이
를 시퀀스로 바꿔야 한다.
Iterator
를
for
내장에서 반환할 수 없기 때문에,
for
내장의 반환 타
입이 최초의 제너레이터에 의해 결정되었다.
➋ 정규 표현식을 사용해서 빈 줄을 걸러낸다.
➌ 지역변수를 정의한다. 공백 압축이 비활성화된 상태면 줄을 바꾸지 않고, 활성화된 상태면
줄에서 연속된 공백을 한 공백으로 압축한다.
➍
yield
를 사용해서 줄을 반환한다. 따라서
for
내장은
Seq
[
String
]
(
http
://
bit
.
ly
/
1E8xLCt
)
을 만들어내며, 이를
apply
가 반환한다.
apply
가 반환하는 실제 컬렉션에 대해서는 잠시 후에
339
7
장
for 내장
다시 살펴볼 것이다.
➎
main
메서드는
for
내장을 사용해서 인자 목록을 처리한다. 각각의 인자는 처리할 파일의 경
로로 취급된다.
➏ 파일 경로가 ‘
-
’ 문자로 시작하는 경우 공백 압축을 활성화한다. 그렇지 않은 경우에는 빈 줄
만 제거한다.
➐ 처리한 모든 줄을
stdout
에 쓴다.
이 파일을
sbt
로 컴파일한다.
sbt
프롬프트 상에서 소스 파일 자체에 대해 실행해보자. 먼저 ‘
-
’
문자를 붙이지 않고 시도해보자. 다음은 출력 중 일부를 보여준다.
>
run
-
main progscala2
.
forcomps
.
RemoveBlanks
\
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
[
info
]
Running
...
RemoveBlanks src
/.../
forcomps
/
RemoveBlanks
.
scala
//
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
package forcomps
object RemoveBlanks
{
/**
*
지정한
입력
파일에서
빈
줄을
제거한다
.
*/
def apply
(
path
:
String
,
compressWhiteSpace
:
Boolean
=
false
):
Seq
[
String
]
=
...
원래 파일에서 빈 줄이 제거되었다. ‘
-
’를 붙이고 실행하면 다음과 같은 결과가 나온다.
>
run
-
main progscala2
.
forcomps
.
RemoveBlanks
\
-
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
[
info
]
Running
...
RemoveBlanks
-
src
/.../
forcomps
/
RemoveBlanks
.
scala
//
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
package forcomps
object RemoveBlanks
{
/**
*
지정한
입력
파일에서
빈
줄을
제거한다
.
*/
def apply
(
path
:
String
,
compressWhiteSpace
:
Boolean
=
false
):
Seq
[
String
]
=
...
340
2
부
기본기 다지기
여기서는 여러 연속된 공백이 한 공백으로 압축되었다.
이 애플리케이션을 변경하면 번호를 추가하거나, 출력을 별도의 파일에 기록하거나, 각종 통계
를 계산하는 등 더 많은 선택 사항을 입력받을 수 있게 할 수 있다. 전형적인 유닉스 스타일의 명
령행 인자처럼
args
배열로부터 명령행 옵션의 개별 요소를 받을 수 있게 하려면 어떻게 해야 할
까?
apply
메서드가 반환하는 실제 컬렉션으로 돌아가 보자.
sbt
콘솔을 시작하면 이를 찾을 수 있다.
>
console
Welcome to Scala version 2
.
11
.
2
(
Java HotSpot
(
TM
)
...).
...
scala
>
val lines
=
forcomps
.
RemoveBlanks
.
apply
(
|
"
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
")
lines
:
Seq
[
String
]
=
Stream
(
//
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
,
?)
scala
>
lines
.
head
res1
:
String
=
//
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
scala
>
lines take 5 foreach println
//
src
/
main
/
scala
/
progscala2
/
forcomps
/
RemoveBlanks
.
scala
package forcomps
object RemoveBlanks
{
/**
*
지정한
입력
파일에서
빈
줄을
제거한다
.
지연
Stream
(
http
://
bit
.
ly
/
1toy0G5
)이 반환된다.
6
.
9
.
1
절 ‘꼬리 재귀와 무한 컬렉션에 대한
순회’에서 이를 소개했다.
REPL
이
lines
를 정의한 다음에 줄
line
을 출력할 때,
Stream
.
toString
메서드가 스트림의 머리(파일의 주석 부분 )를 계산하고, 아직 계산하지 않은 꼬리에 대해서는
물음표를 표시한다.
머리를 요청한 다음 첫 다섯줄을
take
로 가져왔다. 그에 따라 그 다섯줄이 강제로 평가된다. 걸
러내지 않은 전체 파일을 담기엔 요구하는 메모리가 너무 커질 수 있는 매우 큰 파일을 처리할
수도 있기 때문에
Stream
을 사용하는 것이 적절하다. 불행히도
Stream
은 평가한 모든 원소를
기억하기 때문에 커다란 데이터를 모두 읽고 나면 메모리에 전체를 저장하게 된다. (
apply
와
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.