마틴 오더스키 교수님이 코세라에서 진행중인 Functional Programming Principles in Scala 강의(https://www.coursera.org/learn/progfun1)의 3주차 요약.
* 함수형을 잘 모르는 상태에서 요약한 것이므로 내용에 오류가 존재할 수 있음
* 3주차는 주로 클래스와 객체에 대한 내용으로 비교적 익숙했음
클래스 계층
추상 클래스는 구현이 없는 멤버를 포함할 수 있다. 구현이 없으므로 new로 추상 클래스의 인스턴스를 생성할 수 없다.
abstract class IntSet {
def incl(x: Int): IntSet
def contains(x: Int): Boolean
}
IntSet 클래스를 확장(extend)한 두 클래스-Empty, NonEmpty-는 다음과 같이 구현 가능하다.
class Empty extends IntSet {
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = new NonEmpty(x, new Empty, new Empty)
}
class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet {
def contains(x: Int): Boolean =
if (x < elem) left contains x else if (x > elem) right contains x else true
def incl(x:Int): IntSet =
if (x < elem) new NonEmpty(elem, left incl x, right)
else if (x > elem) new NonEmpty(elem, left, right incl x)
else this
}
여기서 Empty와 NonEmpty는 IntSet 타입을 따르며(conform), IntSet 타입이 필요한 곳에 이 두 타입의 객체를 사용할 수 있다.
IntSet을 Empty와 NonEmpty의 상위클래스(superclass)라고 하며, Empty와 NonEmpty를 IntSet의 하위클래스(subclass)라고 한다.
상위 클래스를 지정하지 않으면 java.lang.Object 클래스를 상위 클래스로 가정한다. C 클래스의 직/간접 상위클래스를 베이스 클래스(base class)라고 한다. NonEmpty 클래스의 경우 IntSet과 Object가 베이스 클래스가 된다.
오버라이드를 사용하면 상위 클래스에 정의된 비추상 멤버를 재정의할 수 있다. 다음 코드에서 Sub 클래스의 foo 멤버는 Base 클래스의 foo 멤버를 재정의하고 있다.
abstract class Base {
def foo = 1
def bar: Int
}
class Sub extends Base {
override def foo = 2
def bar = 3
}
오브젝트
스칼라는 한 개의 인스턴스만 갖는 타입인 오브젝트를 지원한다. 예를 들어, NonEmpty는 한 개 인스턴스가 존재하면 되는데 다음과 같이 오브젝트 정의를 사용해서 NonEmpty를 싱글톤으로 정의할 수 있다.
object Empty extends IntSet {
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = new NonEmpty(x, Empty, Empty)
}
동적 바인딩(dynamic binding)
스칼라를 포함한 객체 지향 언어는 동적 바인딩을 구현한다. 동적 바인딩은 메서드를 호출할 때 메서드를 포함한 객체의 런타임 타입에 의존한다는 것을 의미한다.
치환 모델로 동적 바인딩의 계산 과정을 풀면 다음과 같다.
(new NonEmpty(7, Empty, Empty)) contains 7
-> [7/elem][7/x][new NonEmpty(7, Empty, Empty)/this]
if (x < elem) this.left contains x
else if (x > elem) this.right contains x else true
-> if (7 < elem) new NonEmpty(7, Empty, Empty).left contains
else if (7 > 7) new NonEmpty(7, Empty, Empty).right contains 7 else true
-> true
패키지와 임포트
자바처럼 패키지를 이용해서 클래스와 오브젝트를 구성한다. 패키지를 포함한 fully qualified 이름으로 클래스나 오브젝트를 참조할 수 있다.
import를 이용해서 단순 이름으로 사용할 수 있다. scala, java.lang, scala.Predef 오브젝트의 모든 멤버는 자동으로 임포트한다.
트레잇(trait)
스칼라의 클래스는 한 개의 상위클래스만 가질 수 있다. 다중 상속이 필요한 경우 트레잇을 사용할 수 있다. 트레잇은 추상 클래스처럼 추상 멤버와 구현을 가진 멤버를 선언할 수 있다.
trait Planar {
def height: Int
def width: Int
def surfce = height * width
}
클래스, 오브젝트, 트레잇은 최대 한 개의 클래스를 상속할 수 있지만, 트레잇은 여러 개를 상속할 수 있다.
class Square extends Shape with Planar with Movable ...
트레잇은 필드와 메서드 구현을 가질 수 있기 때문에 자바 인터페이스보다 더 강력하다. 클래스가 파라미터를 가질 수 있는 것과 달리 트레잇은 가질 수 없다.
스칼라 클래스 계층
- Any: 모든 타입의 베이스 타입이다. ==, !=, equals, hashCode, toString 메서드를 정의한다.
- AnyRef: 모든 레퍼런스 타입의 기반 타입이다. java.lang.Object에 해당한다. scala.List, scala.Seq 등이 속한다.
- AnyVal: 모든 원시 타입의 기반 타입니다. (scala.Int, scala.Boolean 등이 속한다.)
- Null: 레퍼런스 타입은 값으로 null을 가질 수 있다. null은 Null 타입 값으로 Null 타입은 Object를 상속한 모든 클래스의 하위타입이다.
- Nothing: 모든 타입의 하위 타입으로 스칼라 타입 계층의 최하단에 위치한다.
다형(polymorphism)
다형은 함수 타입이 여러 폼(form)이 된다는 것을 의미한다. 이는 프로그래밍에서 다음을 뜻한다.
- 함수를 여러 타입의 인자에 적용할 수 있거나 -> 지네릭: 함수나 클래스의 인스턴스를 타입 파라미터로 생성
- 타입이 많은 타입의 인스턴스를 가질 수 있다 -> 하위타입: 하위클래스의 인스턴스를 베이스 클래스(형태)로 전달
상속을 통한 다형 : Nil 인스턴스를 List가 필요한 곳에 전달할 수 있다 -> new Cons(10, Nil)
타입파라미터를 통한 다형 : Cons를 여러 타입에 적용할 수 있다. Cons[User](u1, Nil), Cons[Member](m1, Nil)