2. Scala 보편적인Collection
컬렉션 프레임워크는 배열, 리스트, 맵, 집합, 트리와 같이 주어진 타입을 가지는 하나 또는 그 이상의 값을
수집하는 데이터 구조를 제공한다.
스칼라는 JVM 언어이므로 스칼라 코드에서 자바 컬렉션 라이브러리 전체에 접근 하고 사용할 수 있다. 물론,
그렇게 한다면 스칼라만이 가지고 있는 컬렉션의 고차 연산이 주는 영광을 놓치게 된다.
스칼라도 자바와 마찬가지로 고성능의, 객체지향적인, 타입-매개변수화된 컬렉션 프레임워크를 가지고 있다.
그러나 스칼라의 컬렉션은 map, filter, reduce와 같이 짧고 표현력이 있는 표현식으로 데이터를 관리하고
처리하는 고차 연산도 가지고 있다. 또한, 가변적인 컬렉션 타입 계층구조와 불변의 컬렉션 타입 계층구조를
별개로 가지고 있어서(안정성을 위한) 불변의 데이터와 (필요하다면) 가변적인 데이터 사이의 전환을 쉽게
만들어준다.
3. Scala 보편적인Collection
리스트
리스트는 이것을 함수로 호출함으로써 생성할 수 있다. 호출 사이에 그 리스트에 포함될 내용을 쉼표로 구분된
매개변수 형태로 전달한다.
Lisp 스타일의 head()와 tail() 메소드를 사용하여 리스트의 첫
번째 요소와 나머지 요소에 각각 접근해보자.
직접 단일 요소에 접근하려면, 리스트를 함수로 호출하고 그
요소를 가리키는 0부터 시작하는 인덱스를 그 함수에 전달하면
된다.
5. Scala 보편적인Collection
- foreach() 는 함수 (정확하게는 프로시저)를 취하고, 그 함수를
리스트의 모든 항목으로 호출한다.
- map()은 단일 리스트 요소를 다른 값이나 타입으로 전환하는
함수를 취한다.
- reduce()는 두 리스트 요소를 단일 항목으로 결합하는 함수를
취한다.
리스트
foreach(), map(), reduce()를 이용한 예제.
각 함수는 리스트를 반복하고, 전환하며, 리스트를 단일 항목으로 축소한다. 각 메소드에는 함수 리터럴이
전달되는데, 이 함수 리터럴에는 괄호로 묶인 입력 매개변수와 함께 본문이 포함되어 있다.
7. Scala 보편적인Collection
Map
Map은 불변의 키-값 저장소로, 다른 언어에서는 해시맵(hashmap), 딕셔너리(dictionary) 또는 연관 배열
(associative array)로 알려져 있다. Map에 주어진 유일한 키로 저장된 값을 그 키를 이용하여 추출할 수 있다. 키와
값은 타입-매개변수화되어 여러분은 정수를 문자열에 매핑하는 것처럼 쉽게 문자열을 정수에 매핑할 수 있다.
Map은 생성할 때 키-값 쌍을 튜플로 기술하면 된다. 키와 값 튜플을 기술하기 위해 관계 연산자(->)를 사용 할 수
있다.
8. Scala 보편적인Collection
리스트에는 무엇이 있는가?
List는 불변의 재귀적인 데이터 구조이므로 리스트의 각 요소는 자신만의 헤드와 점진적으로 더 짧아지는 테일을
가진다. 이를 사용하여 헤드로 시작하여 잇따른 tail들이 지나가는 길을 만들면서 여러분만의 List 반복자(iterator)를
만들 수 있다.
리스트에서 null로 표현될 수 있는 Nil은 근본적으로 List[Nothing]의 싱글턴 인스턴스다. Nothing 타입의 리스트는
따라서 다른 모든 타입의 리스트와 호환됨, 그들의 종점으로 안전하게 사용될 수 있다.
9. Scala 보편적인Collection
생성 연산자
리스트를 생성하는 다른 방법으로는 이전 절에서 설명한 Nil과의 관계를 이용하는 것이다.
Lisp를 인정하는 또 다른 의미로, 스칼라는 리스트를 만들기 위해 생성(cons, construct의 축양형)연산자의 사용을
지원한다. Nil을 기반으로 항목들을 결합하기 위해 오른쪽 결합형(right-associative) 생성 연산자 ::를 상용하여
전형적인 List(...) 리스트를 만들 수 있다.
10. Scala 보편적인Collection
리스트의 산술연산
아래 링크 참조.
http://www.scala-lang.org/api/2.7.3/scala/List.html
위의 표에서 고차 함수를 발견했는가? 아래 스칼라 REPL에서 실행된 고차 연산 세가지 예제, fitler, partition,
sortBy가 있다.
11. Scala 보편적인Collection
리스트의 산술연산
sortBy 메소드는 리스트 항목들의 순서를 정렬하기 위해 사용할 값을 반환하는 함수를 취하는 반면, filter와 partition
메소드는 조건자 함수를 취한다. 조건자 함수 (predicate function)는 하나의 입력값을 받아서 참 또는 거짓을
반환한다.
이 산술 연산자 메소드에서 중요한 점은 ::, drop, take가 리스트의 앞에서 동작하고, 따라서 성능상의 불이익이
없다는 것이다. List는 연결 리스트이기 때문에 그 앞에 항목들을 추가하거나, 그 앞의 항목들을 제거하는 것은
리스트 전체를 순회할 필요가 없다. 리스트가 짧다면 리스트 순회는 사소한 일이겠지만, 수천, 수백만의 항목들을
가진 리스트라면 리스트를 순회해야 하는 연산은 큰일이 될 수 있다.
그렇다고 해도 이러한 연산들은 리스트의 끝에서 동작하는, 따라서 리스트 전체 순회가 필요한 동반 연산(corollary
operation)들을 가지고 있다. 대규모 리스트로 작업하지 않는 한 메모리 활용을 고려할 필요는 없지만, 일반적으로
리스트의 끝이 아니라 앞에서 연산하는 것이 가장 좋다.
12. Scala 보편적인Collection
리스트 맵핑
Map 메소드는 함수를 취하여 그 함수를 리스트의 모든 요소들에 적용하고, 그 결과를 새로운 리스트에 수집한다.
일반적으로 집합 이론과 수학 분야에서는 매핑한다(map)는 것은 한 집합의 각 요소와 다른 집합의 각 요소 사이에
연관성을 만드는 것이다.
collect : 각 요소를 부분 함수를 사용하여 변환하고, 해당 함수를 적용할 수 있는 요소를 유지함.
flatMap : 주어진 함수를 이용하여 각 요소를 변환하고, 그 결과 리스트를 이 리스트에 평면화(flatten)함.
map : 주어진 함수를 이용하여 각 요소를 변환함.
13. Scala 보편적인Collection
리스트 축소하기
리스트 축소는 컬렉션으로 작업하는 데 있어 가장 보편적인 연산이다/ 등급 리스트를 합산하거나 여러 성능 테스트
결과의 평균 시간을 계산해야 하는가? 만일 컬렉션이 특정 요소를 포함하는지 검사하고 싶거나, 조건자 함수가
리스트의 모든 요소에 대해 '참'을 반환할 것인지를 알고 싶다면? 이들은 리스트를 단일 값으로 축소하는 로직을
사용하기 때문에 모두 리스트 축소 연산이다.
스칼라의 컬렉션은 수학적 축소 연산(예: 리스트의 합계 구하기)과 부울 축소 연산(예: 리스트가 주어진 요소를
포함하는 여부 확인하기)을 지원한다.
max : 리스트의 최댓값 구하기.
min : 리스트의 최솟값 구하기.
product : 리스트의 숫자들을 곱하기
sum : 리스트의 숫자들을 합산하기
14. Scala 보편적인Collection
리스트 축소하기
contains : 리스트가 이 요소를 포함하고 있는지를 검사함.
endWith : 리스트가 주어진 리스트로 끝나는지를 검사함.
exists : 리스트에서 최소 하나의 요소에 대해 조건자가 성립하는지를 검사함.
forall : 리스트의 모든 요소에 대해 조건자가 성립하는지를 검사함.
startsWith : 리스트가 주어진 리스트로 시작하는지를 테스트 함.
16. Scala 보편적인Collection
컬렉션 전환하기
스칼라에서는 타입 간 전환이 쉬워서 하나의 타입으로 설렉션을 생성하여 다른 타입으로 끝낼 수 있다.
mkString : 주어진 구분자를 사용하여 컬렉션을 String으로 만듬
ex ) List(24, 99, 104).mkString(", ")
toBuffer : 불변의 컬렉션을 가변적인 컬렉션으로 전환
ex ) list('f', 't').toBuffer
toList : 컬렉션을 List로 전환
ex ) Map("a" -> 1, "b" -> 2).toList
toMap : 두 요소(길이)로 구성된 튜플의 컬렉션을 Map으로 전환
ex) Set(1 -> true, 3 -> true).toMap
toSet : 컬렉션을 Set으로 전환
ex) List(2, 5, 5, 3, 2).toSet
toString : 컬렉션을 String으로 컬렉션의 타입을 포함하여 만듦.
ex ) List(2, 5, 5, 3, 2).toString
17. Scala 보편적인Collection
자바와 스칼라 컬렉션 호환성
자바와 스칼라 상호작용의 일부는 자바 컬렉션과 스칼라 컬렉션 간 전환하는 것인데, 이 두 컬렉션 타입은
기본적으로 호환되지 않는다.
import 명령어는 JavaConverters와 그 메소드를 현재의 네임스페이스에 추가한다.
asJava : 스칼라와 컬렉션을 그에 대응하는 자바 컬렉션으로 전환함.
ex) List(12, 29).asJava
asScala : 이 자바컬렉션을 그에 대응하는 스칼라 컬렉션으로 전환함.
ex) new java.util.ArrayList(5).asScala
19. Scala 보편적인Collection
컬렉션으로 패턴 매칭하기
패턴 매칭은 스칼라 표준 컬렉션 라이브러리의 그저 평범한 연산이 아니라 이 언어의 핵심 특징이다. 패턴 매칭은
스칼라 데이터 구조를 광범위 하게 적용될 수 있으며, 현명하게 사용한다면 다른 언어에서는 광범위한 작업이
필요한 로직을 짧고 간단하게 만들어 줄 수 있다.
스칼라 컬렉션 라이브러리를 다른 언어들과 구분 짓는 핵심 특징은 불변의 데이터 구조와 고차 연산을 지원한다는
데 있다.
스칼라 핵심 데이터 구조인 List, Map, Set은 불변하는 데이터 구조다. 이들은 크기를 재조정할 수도 없으며, 그 안의
내용을 바꿀 수도 없다.
가변적인 컬렉션 보다 우선권을 주기 위해 불변의 데이터 구조 패키지 (collection.immutable)는 자동으로 스칼라
네임스페이스에 기본적으로 임포트 된다.
20. Scala 보편적인Collection
컬렉션으로 패턴 매칭하기
이러한 우선권 때문에 개발자들이 일반적으로 함수형 프로그래밍 업계에서 '모범 사례'라 할 수 있는 불변의
컬렉션과 불변의 데이터를 사용하도록 유도하는 것을 목표로 한다.
익명 함수를 이용하여 컬렉션을 취하고, 이를 반복 또는 맵핑하는 능력은 루비와 파이썬을 포함한 많은 언어에서
보편적이다.
타입이 안전한 컬렉션을 고차 함수와 사용하면 선언형 프로그래밍을 지원하고, 표현력 있는 코드를 만들 수 있으며,
런타임 타입 전환 에러의 발생이 매우 적다.
스칼라 컬렉션은 모나드 구조(monadic)로, 고차원의 타입-안전한 방식으로 연산을 연결 할 수 있게 해준다.
21. Scala 함수적자료구조
함수적 자료구조 (빨간책)
함수적 프로그램은 변수를 갱신하거나 변이 가능한(mutable) 자료구조를 수정하는 일이 없다고 했다.
그렇다면 자연스럽게 이런 의문이 떠오를 것이다. 함수형 프로그래밍에서 사용할 수 있는 자료구조는 어떤 것일까?
함수적 자료구조의 정의
함수적 자료구조란 오직 순수 함수만으로 조작되는 자료구조이다.
순수함수는 자료를 그 자리에서 변경하거나 기타 부수 효과를 수행하는 일이 없어야 함을 기억하기 바란다.
따라서 함수적 자료구조는 정의에 의해 불변이다.
23. Scala 함수적자료구조
자료 형식을 도입할 때에는 trait 키워드를 사용한다. trait 키워드로 정의하는 ‘특질(trait)’은 하나의 추상
인터페이스로, 필요하다면 일부 메서드의 구현을 담을 수도 있다.
위 코드에서는 List라는 특질을 정의했다. 이 특질에는 메서드가 없다. trait 앞에 sealed를 붙이는 것은 이 특질의
모든 구현이 반드시 이 파일 안에 선언되어야 함을 뜻한다.
그 다음에 있는 case 키워드로 시작하는 두 줄은 List의 두 가지 구현, 즉 두가지 자료 생성자(data constructor)이다
이들은 List가 취할 수 있는 두가지 형태를 나타낸다.
매개변수 A를 둔 것은 A 매개변수를 사용함으로써, 목록에 담긴 요소들의 형식에 대해 다형성이 적용되는 List 자료
형식이 만들어진다.
Int, Double, String 등 사용할 수 있게된다. 변수 A에 있는 +는 공변(covariant)을 뜻한다.
(공변과 불변은 빨간책 40p를 참조하라.)
24. Scala 함수적자료구조
위에서 보여준 trait으로 정의한 List에서 case Cons[+A](head : A, tail : List[A]) extends List[A] 는 Cons라는 형태가
List[A]를 상속받는 구조이다. 스칼라에서 List는 연결리스트이다.
이것을 함수형 자료구조로 나타냈을 때 Cons(head :A, tail : List[A]) 로 나타낼 수 있다는 것이다.
풀어서 이야기하면 Cons(값, 링크) 로 이해하면 편하다. 하지만 이곳에서는 결국 Cons(값,Cons(값,Cons(값,...)))
형태가 되는 것이다. 이것이 연결리스트를 함수형 자료구조로 표현한 것이다.
ex) function(value, function) 형태
25. Scala 함수적자료구조
패턴 부합은 표현식의 구조를 따라 내려가면서 그 구조의 부분 표현식을 추출하는 복잡한 switch문과 비슷하게
작동한다.
패턴 부합 구문은 표현식으로 시작해서 다음 키워드 match가 오고, 그 다음에 일련의 경우(case) 문들이 {}로
감싸인 형태이다.
부합의 각 경우 문은 =>의 좌변에 패턴이 있고, 우변에 결과가 있는 형태다.
대상이 경우 문의 패턴과 부합하면 그 경우 문의 결과가 전체 부합 표현식의 결과가 된다.
만일 대상과 부합하는 패턴이 여러 개이면 스칼라는 처음으로 부합하는 경우 문을 선택한다.
ex)
List(1,2,3) match { case _ => 42 } 이 패턴식에는 결국 42가 된다.
임의 표현식 _ 를 사용했기 때문에 any 타입으로도 매치가 됨으로 42가 된다.
(‘_’ 대신 x나 foo를 사용해도 되지만, 경우 문의 결과 안에서 그 값이 무시되는 변수를 나타낼 때에는 이처럼 ‘_’를
사용하는 것이 보통이다.)
26. Scala 함수적자료구조
List(1, 2, 3) match { case Cons(_, t) => t } 결과는 무엇일까?
답은 List(2, 3) 이다. 위 case문은 head를 제외한 tail만 반환하라는 뜻이다.
List(1, 2, 3) match { case Nil => 42 } 결과는 무엇일까?
답은 MatchError 이다. Nil ( null) 이다. 따라서 List가 Nil 이 아니기 부합되는 것이 하나도 없음을 뜻한다.
아래 문제를 한번 풀어보자.
다음 패턴 부합 표현식의 결과는 무엇인가?
val x = List(1, 2, 3, 4, 5) match {
case Cons(x, Cons(2, Cons(4, _))) => x
case Nil => 42
case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
case Cons(h, t) => h + sum(t)
case _ => 101
}