-
스칼라에서 타입을 정의하는 방법컴공지식/프로그래밍언어론 2024. 8. 31. 22:27
trait type-id
trait는 스칼라에서 타입을 정의하는 일종의 설계도라고 할 수 있다.
자바의 인터페이스(Java interface)와 비슷하지만, 스칼라에선 조금 다르게 동작한다.
trait는 클래스가 구현해야 할 메서드나 필드를 정의할 수 있다.
하지만 실제로 그 메서드를 구현하지는 않는다.
그 역할은 trait를 사용하는 클래스가 하게 된다.
중요한 점은 스칼라에서는 여러 trait을 상속받을 수 있다는 것이다.
즉, 다중 상속이 가능하다.
case class variant_id (field_id: type) ... extends type-id
case class는 스칼라에서 특별한 종류의 클래스다.
자주 쓰이는 패턴을 간단하게 표현할 수 있게 해준다.
case class는 자동으로 equals, hashCode, toString 같은 메서드를 만들어주기 때문에 아주 유용하다.
여기서 variant_id는 클래스의 이름이 되고, field_id는 클래스의 필드를 의미한다.
type은 그 필드의 타입이다.
마지막으로 extends type-id는 이 case class가 어떤 trait나 다른 클래스를 상속받는지를 나타낸다.
스칼라에선 extends를 사용해 상속 관계를 표시한다.
trait는 타입이고, 여러 개의 trait를 상속받을 수 있다.
자바 인터페이스와 비슷하지만, 스칼라에선 더 강력하다.. 여러 trait를 결합해 강력한 타입을 만들 수 있다.
요약하자면
trait: 인터페이스 같은 개념, 클래스로 하여금 특정 메서드를 구현하게 하는 설계도.
case class: 스칼라에서 자주 사용하는 클래스 패턴, 보일러플레이트 코드를 줄여주는 특별한 클래스.
상속: extends를 통해 trait나 다른 클래스를 상속받을 수 있다.
다음은 스칼라에서 trait와 case class를 어떻게 사용하는지 보여주는 예시다
trait Shape
case class Triangle(a: Int, b: Int, c: Int) extends Shape
case class Rectangle(h: Int, w: Int) extends Shape
case class Square(side: Int) extends Shape여기서 Shape는 trait로 정의돼 있다.
trait는 일종의 추상적인 타입이자 인터페이스 역할을 하는데, 이 경우엔 "모든 도형은 Shape라는 공통 타입을 가져야 한다"는 개념을 나타내고 있다.
다른 클래스들은 Shape를 상속받아야 도형으로 취급될 수 있다.
이 예시는 다양한 도형들(Triangle, Rectangle, Square)이 모두 공통된 Shape라는 trait를 상속받아서, 같은 타입으로 취급될 수 있음을 보여준다.
스칼라에서는 이런 식으로 공통된 특성을 정의하고, 그 특성을 여러 클래스에서 공유할 수 있다.
이렇게 하면 나중에 Shape 타입을 사용하는 함수나 메서드에서 삼각형, 직사각형, 정사각형을 모두 같은 방식으로 처리할 수 있게된다.
trait를 사용하지 않는다면 어떤 점이 불편해지는지 예시를 들어서 설명해보겠다.
먼저, trait를 사용하지 않고 각각의 도형을 개별적으로 처리하는 코드를 생각해보자.
case class Triangle(a: Int, b: Int, c: Int)
case class Rectangle(h: Int, w: Int)
case class Square(side: Int)
def describeTriangle(triangle: Triangle): String =
s"Triangle with sides ${triangle.a}, ${triangle.b}, ${triangle.c}"
def describeRectangle(rectangle: Rectangle): String =
s"Rectangle with height ${rectangle.h} and width ${rectangle.w}"
def describeSquare(square: Square): String =
s"Square with side ${square.side}"이렇게 하면 각각의 도형에 대해 별도의 함수를 작성해야 한다. 이 함수들은 기본적으로 비슷한 일을 하지만, 각각의 도형에 대해 따로 작성해야 하니까 코드가 중복되고, 유지보수가 어려워진다.
새로운 도형(예: Circle)을 추가하려면, describeCircle 같은 새로운 함수를 추가해야 한다.
이 과정에서 기존 함수들과의 일관성을 유지하기 어렵고, 실수할 가능성도 커진다..
만약 도형을 처리하는 코드가 여러 군데 있다면, 그 모든 곳에서 새로운 도형에 대한 처리를 추가해야 해. 그러면 코드가 산재하게 되고, 수정해야 할 곳을 일일이 찾아다니는 게 고역이 된다.
그리고 Shape라는 공통 타입이 없기 때문에, 다양한 도형을 한꺼번에 처리할 수 있는 일반적인 함수나 메서드를 작성하기 어렵다.
예를 들어, List[Shape] 같은 리스트를 만들어서 도형들을 한꺼번에 처리할 수가 없다.
대신 각각의 리스트(예: List[Triangle], List[Rectangle])를 따로 만들어야 하고, 그걸 처리하기 위한 코드도 따로 작성해야 한다...
이제 다시 trait를 사용하는 예제를 보자
trait Shape
case class Triangle(a: Int, b: Int, c: Int) extends Shape
case class Rectangle(h: Int, w: Int) extends Shape
case class Square(side: Int) extends Shape
def describeShape(shape: Shape): String = shape match {
case Triangle(a, b, c) => s"Triangle with sides $a, $b, $c"
case Rectangle(h, w) => s"Rectangle with height $h and width $w"
case Square(side) => s"Square with side $side"
}이 함수에서, Shape 타입의 객체가 들어오면, 패턴 매칭(match 문)을 통해 그 객체가 Triangle, Rectangle, Square 중 어떤 것인지 확인하고, 해당하는 케이스에 따라 다른 문자열을 반환한다.
이렇게 하면 코드의 중복이 줄고, 확장성과 유연성이 높아진다.
참고로 스칼라의 Syntax에 대해서 부연설명을 하자면
문자열 앞에 s를 붙이는 이유는 문자열 안에 변수를 삽입하기 위해서 사용하는 것이다.
그러면 +를 사용하지 않고 ${변수}를 이용해 변수를 출력하는게 가능해진다.
'컴공지식 > 프로그래밍언어론' 카테고리의 다른 글
패턴 매칭 vs if-else 절 (0) 2024.08.31 스칼라의 패턴 매칭 (0) 2024.08.31 Scalar의 assert (0) 2024.08.31 스칼라에서 함수 정의하는 방법 (0) 2024.08.31 인터프리터와 컴파일러의 차이 (1) 2024.08.31