293
6
장
스칼라 함수형 프로그래밍
scala
>
val finicky2
=
Function
.
unlift
(
finickyOption
)
finicky2
:
PartialFunction
[
String
,
String
]
=
<
function1
>
scala
>
finicky2
("
finicky
")
res16
:
String
=
FINICKY
scala
>
finicky2
("
other
")
scala
.
MatchError
:
other
(
of class java
.
lang
.
String
)
...
함수를 끌어올리는 개념의 다른 예다. 부분 함수가 있고, 그 함수에서 예외가 발생하는 것을 바
라지 않는다면, 해당 함수를 끌어올려서
Option
을 반환하도록 만들 수 있다. 또한 함수를 ‘끌어
내려서’ 옵션을 반환하는 함수를 부분 함수로 바꿀 수도 있다.
6.7
함수형 데이터 구조
표현하고자 하는 개념에 맞는 임의의 클래스를 만드는 것이 훨씬 일반적인 객체지향 언어에 비
해 함수형 프로그래밍에서는 핵심 데이터 구조와 알고리즘을 사용하는 것을 매우 강조한다. 코
드 재사용
code
reuse
이 객체지향 프로그래밍의 약속이긴 하지만, 이런 식의 임의 클래스가 너무
많으면 그런 목표를 달성하기 어렵다. 따라서 어쩌면 모순적으로, 객체지향 프로그램과 비교할
때 함수형 프로그램이 더 간결하면서도 코드를 더 잘 재활용하는 경향이 있다. 무언가를 다시 발
명하는 일도 적고, 논리를 구현할 때 핵심 데이터 구조와 알고리즘에 의존하도록 강조하기 때문
이다.
이 핵심 집합에 어떤 것이 들어 있는지는 언어에 따라 다르다. 하지만 최소한 전형적으로 들어
가는 것은 리스트
list
, 벡터
vector
, 배열
array
이며, 맵
map
과 집합
set
도 들어갈 수 있다. 각 컬렉션은
부수 효과가 없는 여러 고차 함수를 함께 지원한다. 이런 함수를 콤비네이터라고 부르며
map
,
filter
,
fold
등이 있다. 이런 콤비네이터에 대해 배우고 나면, 데이터 접근이나 성능에 대한
여러분의 필요에 따라 적절한 컬렉션을 선택하고, 같은 일련의 콤비네이터를 사용해서 데이터
를 조작하면 된다. 이런 컬렉션은 소프트웨어 개발 과정에 가장 성공적으로 코드를 재활용하고
조합할 수 있는 도구다.
294
2
부
기본기 다지기
6.7.1
시퀀스
스칼라 프로그램에서 가장 자주 사용하는 데이터 구조 몇몇을 살펴보자.
많은 데이터 구조가 순차적
sequential
이다. 즉, 원소를 미리 정해진 순서에 따라 순회 가능하며, 그
순서는 원소를 삽입한 순서거나, 다른 방식에 따라 정렬된 순서다.
collection
.
Seq
(
http
://
bit
.
ly
/
1voJwOs
) 트레이트는 모든 변경 가능하거나 불변인 순차적 타입에 대한 추상화다. 자식
트레이트로
collection
.
mutable
.
Seq
(
http
://
bit
.
ly
/
1tpocKD
)와
collection
.
immutable
.
Seq
(
http
://
bit
.
ly
/
1wO3Ih1
)가 각각 변경 가능한 시퀀스와 변경 불가능한 시퀀스를 표현한다.
연결 리스트는, 최초의 함수형 프로그래밍 언어인 리스프 이래, 함수 프로그램에서 가장 자주 사
용하는 시퀀스다.
관례적으로 원소를 리스트에 추가하면 기존 리스트의 맨 앞에 들어가서, 새로운 리스트의 ‘머리’
가 된다. 나머지 기존 리스트는 변하지 않고, 새 리스트의 ‘꼬리’가 된다. [그림
6
-
1
]은 두 개의
리스트
List
(
1
,
2
,
3
,
4
,
5
)
와
List
(
2
,
3
,
4
,
5
)
를 보여주며, 후자는 전자의 꼬리다.
그림
6-1
두개의연결리스트
새 리스트를 기존 리스트로부터
O
(
1
) 연산으로 만들 수 있음에 유의하라. 원래 리스트를 꼬리
로 공유하며, 새 머리 원소로부터 기존 리스트로 가는 새로운 링크만 만들면 된다. 이 예제는 함
수형 데이터 구조의 중요한 아이디어인, 복사에 드는 비용을 최소화하기 위해 구조를 공유하는
것을 보여주는 첫 번째 예제다. 불변성을 지원하기 위해서는 복사본을 최소 비용으로 만들 수 있
는 능력이 필요하다.
원래 리스트를 사용하는 다른 코드는 새로운 리스트의 존재를 알 수 없다. 리스트는 변경 불가
능하다. 따라서 다른 스레드에서 새로운 리스트를 만들어낸 코드가 리스트의 상태를 변경해서,
다른 코드가 그로 인해 놀라운 일을 겪는 경우는 없다.
연결 리스트를 구축하는 방식을 보면 리스트의 길이를 계산하기 위해
O
(
N
)이 걸린다는 사실
은 분명하다. 리스트의 머리를 통해 순회를 진행해야 하는 다른 모든 연산도 마찬가지다.
295
6
장
스칼라 함수형 프로그래밍
다음
REPL
세션은 스칼라에서
List
(
http
://
bit
.
ly
/
1toub3N
)를 만드는 것을 보여준다.
//
src
/
main
/
scala
/
progscala2
/
fp
/
datastructs
/
list
.
sc
scala
>
val list1
=
List
("
Programming
",
"
Scala
")
list1
:
Seq
[
String
]
=
List
(
Programming
,
Scala
)
scala
>
val list2
=
"
People
"
::
"
should
"
::
"
read
"
::
list1
list2
:
Seq
[
String
]
=
List
(
People
,
should
,
read
,
Programming
,
Scala
)
List
.
apply
(
http
://
bit
.
ly
/
10FrqQC
) 메서드를 사용해서 리스트를 만들 수 있다. 그 후 콘즈
cons
(
construct
에서 온 말 )라고도 하는
::
메서드를 사용해서 새로운 원소를 앞에 추가한 새
리스트를 만들 수 있다. 여기서는 중위 표기법을 사용해서 마침표와 괄호를 생략했다. 콜론
(
:
)으로 메서드 이름이 끝나면 오른쪽으로 결합된다는 점을 기억하라. 따라서
x
::
list
는
list
.:: (
x
)
다.
또한
::
메서드를 사용해서 빈 리스트인
Nil
(
http
://
bit
.
ly
/
1wQuIOs
) 앞에 원소를 추가하는
방식으로 리스트를 만들 수도 있다.
scala
>
val list3
=
"
Programming
"
::
"
Scala
"
::
Nil
list3
:
Seq
[
String
]
=
List
(
Programming
,
Scala
)
scala
>
val list4
=
"
People
"
::
"
should
"
::
"
read
"
::
Nil
list4
:
Seq
[
String
]
=
List
(
People
,
should
,
read
)
Nil
은
List
.
empty
[
Nothing
]
과 같다.
Nothing
(
http
://
bit
.
ly
/
1wObBSq
)은 스칼라의 모든
다른 타입의 서브타입이다.
10
.
2
절 ‘스칼라 타입 계층구조’를 보면
Nothing
에 대한 여러 내용
을 볼 수 있으며, 이 문맥에서 왜
Nothing
을 사용해야 하는지에 대해 볼 수 있다.
두 리스트 (또는 임의의 시퀀스 )를 함께 붙이고 싶다면
++
메서드를 사용한다.
scala
>
val list5
=
list4
++
list3
list5
:
Seq
[
String
]
=
List
(
People
,
should
,
read
,
Programming
,
Scala
)
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.