11장부터 이어서 계속
- 01~10장: https://doocong.com/languages/scala-1/
- 11~20장: https://doocong.com/languages/scala-2/
- 21~30장: https://doocong.com/languages/scala-3/
Scala’s Hirarchy
Any
:abstract class
, 모든 타입의 부모 타입==
,!=
,equals
,hashCode
,toString
: 공통 함수들
AnyVal
:abstract class
, 모든 vaule 타입의 부모 타입- value 타입은 비교시 값을 직접 비교함
- value 타입은
new
로 인스턴스 생성 못함
AnyRef
:class
, 모든 ref 타입의 부모 타입- ref 타입은 비교시 주소를 비교함
Null
:abstract class
, 모든 ref 타입의 자식 타입null
:object
,Null
의 유일한 인스턴스, value 타입에 대입 불가Nothing
:abstract class
, 모든 타입의 자식 타입, 인스턴스 없음Unit
:abstract class
, Vaule 타입임, 함수의 리턴이 없음을 나타냄- 모든 객체가
Unit
으로 암시적으로 변환될 수 있으며, 이 경우 데이터가 삭제됨
- 모든 객체가
None
:object
, nullable한 비어있는 값을 리턴할 때 사용됨
Value type
- 컴파일러가 자바 바이트코드로 변환시 알아서 처리
- 대부분 primitve로 처리해서 효율성 추구
- 필요할 때는
java.lang.Integer
로 박싱됨- 확장 함수를 부르거나
Any
로 대입될때
- 동등성은 값을 기준으로 계산됨
Traits
class
와 거의 동일한 기능을 할 수 있음- 메소드, 필드 선언 등등
- java8의 interface와 매우 유사하다.
- 디폴트 메소드를 허용하며
- rich interface를 추구한다.
- 중요한 기능들
- 다중상속:
extends
,with
- 인스턴스에 붙이기 가능
- 동적 super call 바인딩
- Stackable
- 다중상속:
- 한가지 단점
- 외부에서 사용 불가능, 밖에 제공할 목적이면 class 써야함
Traits | Abstract Class | |
---|---|---|
multiple inheritance | support | not support |
add to instance | possible | impossible |
constructor parameters | impossible to have | possible to have |
Java interoperable | not interoperable | interoperable |
super call binding | dynamic | static |
Stackable | stackable | not stackable |
Stackable Modification
- 다중 상속이 가능
- 다중 상속으로 인한 함수 이름 충돌시, 기본적으로 컴파일 에러가 남
- 같은
class
를 상속받는 경우- 같은 이름의 함수를 확장할 수 있음
- 더 나중(오른쪽)에 붙인
trait
의 함수가 먼저 호출됨 super
는 왼쪽trait
의 함수를 지칭함
abstract class
를 상속받고, 구현이 존재하지 않는 함수가 충돌할 경우abstract override
로 함수 선언해야함- 이 경우에도
super
를 쓸 수 있음 - 그리고 함수가 런타임에 바인딩됨
1 |
|
Linearliztion
- 다중상속으로 얽히 Trait의 호출 관계를 풀어내는 과정
- 호출시에는 오른쪽에서 왼쪽으로
- 선형화로 순선 정할시에는 왼쪽에서 오른쪽으로
- 앞에서 이미 탐색한 지점을 만날때까지 루트로부터 최단경로를 앞에다가 붙여주면 됨
1 |
|
Packages and Imports
package
- 자바랑 똑같이 제일 앞줄에 패키지 써주면 됨
- 중첩 형식 가능
- C++의 namespace처럼 쓸 수 있고, 스코프를 공유함
- 즉 중첩되어 있을때는 루트부터 경로를 쓰지 않아도 됨
_root_
로 최 외곽 패키지에도 접근 가능
import
- 파이썬처럼 on-demand로 import가 가능
- 와일드카드 혹은 선택적 임포트
import A._
패키지 내의 모든것 가져오기import A.{B, C}
선택적으로 가져오기import A.{B => D}
이름 바꿔서 선택적으로 가져오기import A.{B => D, _}
이름 바꾸고, 전체 다 가져오기import A.{B => _, _}
B
는 제외하고 다 가져오기
- 3개 패키지는 암시적으로 import됨
java.lang._
,scala._
,Predef._
Access Modifier
- 블록단위의 스코프를 가짐
- 블록 내에서 변수를 재정의 할 수 있음(like C++, not-like Java)
- 인터프리터 내에서는 항상 재정의 가능
- 클래스 변수의 접근 제한자 별 스코프는 아래 표와 같음
- top-level private과 protected는 같은 효과를 냄
- 참고 블로그
protected
는 java 와 효과범위가 다름
private[X]
로 접근 가능 범위를 제한할 수 있음- X는 패키지, 클래스
this
가 될 수 있음
- X는 패키지, 클래스
Modifier | Class | Companion | Subclass | Package | World |
---|---|---|---|---|---|
no modifier | O | O | O | O | O |
protected | O | O | O | X | X |
private | O | O | X | X | X |
top-level private | O | O | O | O | X |
Assertions and Unit Testing
- 생략
Case Classes and Pattern Matching
case class
case class
로 선언, 클래스에 여러 기능 추가val
없이 선언해도 인자들이 읽기전용 필드로 등록됨equals
,hashCode
,copy
,toString
추가- 컴패니언 객체에
apply
등록:new
없이 생성 가능 - 컴패니언 객체에
unapply
등록: 패턴 매칭에 사용 가능해짐
Pattern matching
a match { case ... }
구문을 이용해 패턴매칭이 가능함- if문, 변수 선언을 효과적으로 줄일 수 있는 강력한 기능 제공
match
와case
는 별개로 사용 가능- pattern guard로
case
와 함께if
를 사용 가능- 조건에 합치되지 않을 경우 패턴을 스킵하고 다음 패턴을 봄
- 패턴의 중첩 여부를 컴파일러가 알고 있음
- unreached 패턴이 나오면 컴파일 에러가 발생
Pattern의 종류
1 |
|
sealed class
sealed
를 붙이면case
클래스들을 enum처럼 쓸 수 있음- kotlin 처럼 다중 중첩하는것은 권장되지 않음
- 한 파일 내에서만 확장 가능, 혹은 블록으로 묶어서 확장
- 컴파일러가 빠진 케이스에 대해 오류 뿌려줌
@uncheched
를 쓰면 조건 누락 오류 무시 가능
Option type
- Kotlin의
?
nullable 타입과 동일함 ?.
처럼 널접근 연산자는 없음(만들어 쓰면 되긴 함)None
을 null대신 씀Some(Value)
- 생성자 패턴으로 캡슐화를 풀어 패턴매칭
- nullable로 만들거나
Patterns everywhere
- variable binding
- c++의 structured binding과 같은 컨셉
- 단 생성자, tuple 패턴과 조합해서 폭넓게 활용 가능
case
를 partial function으로 활용- case 블록을 함수의 body로 쓸 수 있음
- 인자가 한개인 함수로 취급되며
- 인자는 자동으로 case 문들과 매칭 시도함
- 마치 람다처럼 사용하는 것이 가능
for
문에서 사용 가능<-
로 변순 대입받을때 사용 가능- variable binding과 동일하게 작동함
- 단 매칭되지 않을 경우, 해당 요소를 스킵해버림
1 |
|
Working with Lists
- 충격적이게도, 기본 리스트가 연결리스트이다.
- random access,
last()
, 심지어length
에 선형시간이 든다. - 리스트는 재귀적으로 선언되어 있으므로
- 함수형 프로그래밍과 찰떡궁합이긴 하다.
- 타입 파라미미터는 공변성을 보장한다.
B >: A
일 때List[B] >: List[A]
임을 보장해준다.
a :: xs
로 리스트 앞에 원소 추가 가능 $O(1)$xa ::: xb
로 두 리스트를 이어붙이기 가능 $O(xa)$head
가장 앞 원소,tail
앞 원소 제외한 뒤쪽 리스트last
,init
는 가장 뒤 원소와, 그것을 뺀 앞쪽 리스트 (선형시간 걸림)reverse
,drop
,take
,splitAt
,zip
등의 멤버함수 있음map
,filter
등 자바 스트림 함수들도 있음List.range
,List.make
: 팩토리 함수들
- 패턴 매칭 가능
scala.::
가 정의되어 있기 때문case a :: xs =>
처럼 쓸 수 있음
- reduce 연산자
/:
,:\
- 트리 모양을 연상할것
/:
는 왼쪽부터 계산:\
는 오른쪽부터 계산초기값 /: 리스트 ((누적값, 현재 요소) => 새 누적값)
1 |
|
Collections
Iterable
: 순회 가능한 최상위 traitmap
,flatMap
,filter
등 제공
Iterable
의 자손trait
들Seq
: 순서가 존재Set
: 존재 여부를 저장Map
: key-value를 저장
Iterator
- 한번만 순회 가능한 콜랙션
next
,hasNext
메서드로 순회함
- kotlin의 불변 자료구조와 달리, 불변 자료구조가 데이터 변형 API를 제공해줌
- 예를 들면
List
는 불변이지만,::
로 늘릴 수 있음 ArraySeq
,Queue
,Stack
모두 불변이지만, 값을 변경한 새로운것 생성 가능
- 예를 들면
- scala collection은 거의 타입 파라미터의 공변성을 보장
- 불변 자료구조는 타입 공변성이 보장됨
- 그러므로 타입이 짬뽕된 리스트를 만드는게 가능함
"abc" :: List(1,2) == List<Matchable>("abc", 1, 2)
- 공변성이 발동할 경우 두 타입의 LCA를 찾는듯 함
- kotlin에서는 불변 자료구조의 수정이 불가능해서 생각할 필요가 없었음
- 아래 파트는 책이 내용이 너무 오래 전 것이라 스칼라 공식문서를 참고함
immutable sequence
List
- 연결리스트, 재귀적 구조
head
,tail
,preappend
에 상수시간이 걸림- 임의 접근, length 등에 선형시간이 걸림
LazyList
(구Stream
)#::
혹은LazyList.cons
로 이어서 만든다.LazyList.empty
로Nil
을 대체#::
의 앞뒤로 들어가는 head와 tail모두 call-by-name- 즉 값의 산출이 직접 읽을때만 이뤄진다.
- 무한정 늘어나는 수열을 다룰때 유용하다.
ArraySeq
- Java의 Array와 거의 똑같은 고정 크기 배열이다.
head
,apply
: 임의 접근이 상수시간만에 가능- 단 복사 후 변경을 위한 함수들을 제공 (
tail
,update
,prepend
,append
)
Vector
- balacned radix tree이 내부 구현됨
- 32개 자녀를 가지는 tree node들로 구성됨
- radix tree이므로 모든 연산이 상수시간
- 변경, 추가 모두에서 딱 필요한 부분만 변경해줌
- 단 상수 자체가 크므로 필요할때만 쓰는게 좋음
- immutable
Queue
- 내부 구현은 두개의 List,
in
,out
을 유지하도록 되어있음 in
에 쭉 밀어넣다가, 빼내야 할 때,in
에 있는것을 뒤집어서 out에 넣어줌enqueue
,dequeue
모두 리턴을 받아서 써야함enqueue
는 상수 시간만 걸림dequeue
는 amortized 상수 시간이 걸림dequeue
는 실질적으로 Queue의head
와tail
을 나누는 것임- 즉
head
,tail
,append
제외한 나머지는 선형시간이 걸림
- 내부 구현은 두개의 List,
immutable map/set
- immutable Set, Map 모두 공통적으로
+
연산으로 확장함+
된 결과를 새 변수에 리턴받는 식으로 확장var
로 선언한 경우 심지어+=
도 사용 가능- 단
var
로+=
사용하면, 공변성은 제대로 작동 안함
HashSet
,HashMap
- 순서 보존 안되는 해시 자료구조
- 내부적으로는 32개 자녀를 가지는 tree 구조임
- 5개 미만의 경우, 따로 전용 클래스로 생성됨
- 마찬가지로 새 요소를 추가한 후 리턴받는 식으로 확장
TreeSet
,TreeMap
- balanced binary red-black tree로 구현됨
- 마찬가지로 새 요소를 추가한 후 리턴받는 식으로 확장
- 모든 연산이 로그 시간 안에 완료됨
BitSet
- 비트맵으로 Set을 저장
- density가 높을 때 유리하다.
- lookup은 상수시간으로 매우 빠르고,
add
,remove
는 복사해야 하므로 비트맵이 커질수록 느려진다.
VectorMap
,LinkedHashMap
- hash-map에 순서를 보존하는 특성을 붙인것
- hash-map 앞단에 vector가 있는지, 연결 리스트가 있는지에 따라 달라짐
ListMap
- 그냥 리스트로 페어를 저장한 것
- 거의 사용되지 않음, 모든 연산이 선형 복잡도
- 제일 앞 요소를 압도적으로 많이 접근할 때 사용
mutable sequence
- immutable과 달리 내부 데이터를 직접 바꿀 수 있음
ArrayBuffer
,StringBuilder
- c++ vector와 동일한 사이즈를 2배씩 키우는 가변 길이 배열
- 뒤쪽 삽입, 임의 접근은 상수 시간
- 앞이나 중간에 삽입은 선형 시간
- StringBuilder는 타입이 char로 고정되고, 여러 기능이 추가됨
ListBuffer
- 내부에 배열 대신 리스트를 사용함
- 가장 앞 조회, 앞뒤로 붙일때 상수 시간
- 나머지 모든 연산은 선형시간 걸림
ListBuffer
로 쌓다가, 최종적으로List
로 바꿀때 유리
ArrayDeque
,Stack
,Queue
ArrayDeqeue
= resizable circular bufferStack
,Queue
는ArrayDequeue
를 사용함- 특성이 거의
ArrayBuffer
와 같음 - 다만 prepend에 상수 시간만 걸림
mutable set/map
ArraySeq
,HashMap
,HashSet
- immutable인 경우와 모든 특성이 같음
- 다만 값을 수정할 수 있음
WeakHashMap
- 다른 참조가 없으면 GC 대상이 되도록 하는 해시맵
- 캐시를 만들때 유용함
- Concurrent Map
- java버전: 읽기시에 락 걸지 말고, 쓰기시에만 락 검
- TrieMap: 아예 락 프리로 구현함
- mutable
BitSet
- 복사를 안해도 되므로, 수정이나 삭제에 상수 시간 복잡도
- 나머지는 똑같음
Statful Objects
- kotlin처럼 getter, setter로 프로퍼티를 생성 가능
1 |
|
Type Parameterization
- java vs kotlin vs scala generics
- 함수, 클래스 선언시 upper-bound를 정하는 기능: java, kotlin, scala
- 함수, 클래스 선언시 lower-bound를 정하는 기능: scala, kotlin
- 메소드 선언시 클래스의 타입 파라미터 이용해 bound 정하기: scala
- use-site variance like
<? extends Int>
: java, kotlin
array
variance- java = covariant
- kotlin, scala = invariant
var
on covariant- 그냥은
var x: T
를 선언 못함 - kotlin에서는
private var x: T
로 하면 covariant 타입 선언이 가능함 - scala에서는
private[this] var x: T
로 해야함 - companion object가 블록 밖에서 private 변수 접근이 가능하기 때문
- 그냥은
Checking variance
- variance 판단이 기본적으로 완전 복잡함
- 클래스 내의 타입 위치에 따라
+
,-
위치를 나눌 수 있음+
: 리턴타입,val
,private[this] val
변수 타입, lower bound-
: 메소드 인자의 타입, 제너릭의 메소드의 타입 파라미터, upper bound
- 반공변 위치에 있는 중첩된 타입 파라미터는 공변성이 역전됨
- kotlin도 똑같지만, kotlin을 공부할때는 몰랐었음 -_-..
1 |
|
Lower/upper bound
- 타입 파라미터에 lower/upper bound를 부여할 수 있다.
- 특히 메소드에서는 클래스의 타입 파라미터를 바운드 조건으로 쓸 수 있다.
- kotlin, java에서는 안되는 기능
+P
를 인자로,-M
은 리턴으로 못쓰지만[W >: P]
,[W <: M]
인 W는 인자, 리턴 다 자유롭게 쓸 수 있다.
1 |
|
Abstract Member
1 |
|
- abstract type mebmers
- 항상
class
혹은trait
의 멤버로만 쓸 수 있음 - alias 로 사용하려는 목적
- generic 처럼 타입 상한, 하한 정의 가능
type T <: Food
- path-dependent type: 타입을 마치 변수처럼 활용할 수 있게 해줌
- 항상
- abstract
val
,var
val
: subclass에 멤버 변수 선언을 강제하게 됨var
: subclass에 getter, setter 구현을 강제
- trait의
abstract val
초기화- 생성자 사용이 불가능함
- 생성 후 필드 초기화:
new ExampleTrait { val valMember = 1 }
- 초기화 후 생성
new { val valMember = 1 } with ExampleTrait
lazy val
활용
lazy val
vsdef
- 한번 실행(lazy) vs 매번 실행(def)