본문 바로가기

Languages/Scala

[Scala Cats] 스칼라 타입 클래스 기초

반응형

이미지 출처 : Pixabay

귀여운 고양이로 시작합니다.

스칼라 Cats는 스칼라에서의 Functional한 프로그래밍을 위한 추상화를 제공하는 라이브러리입니다.

 

typelevel/cats

Lightweight, modular, and extensible library for functional programming. - typelevel/cats

github.com

참고문헌은 Scala with cats [무료 pdf] 이며, 거의 번역 위주의 글이 될 것 같습니다.

Scala with Cats 책 순서를 따라서 포스팅을 진행할 예정이며, 예제들은 책 내용을 참고하여 직접 작성한 내용들로 사용할 예정입니다.

 

들어가기 전에, 모나드 괴담을 읽고 오시는 것을 추천드립니다.

 

모나드 괴담

 

xtendo.org

또 하나의 모나드 튜토리얼이 추가될 것 같아요..


Type class 유형에는 세 가지 중요한 구성 요소가 있는데, Type class 그 자신과 특정 Type에 대한 인스턴스, 그리고 사용자에게 노출될 인터페이스 메소드가 바로 그 주인공입니다.

1. Type Class

Type Class는 구현하고자 하는 기능의 인터페이스 혹은 API를 의미합니다.

무엇이든 전송하기 위해 직렬화한 다음, 메시지로 포장하는 기능을 구현한다면 다음과 같은 trait을 작성할 수 있을 것입니다.

sealed trait Message

trait Serializer[A] {
  def serialize(thing: A) : Message
}

 

여기서 Serializer가 바로 type class가 됩니다.

 

2. Type Instance

다음으로, 스칼라 표준 라이브러리 및 위에서 작성한 (사용자가 작성한) 도메인 모델 및 에서 제공하는 타입을 포함하여 우리가 작성한 타입 클래스에 대한 구현부를 제공하는 것을 위 타입 클래스에 대한 Type Instance라 부릅니다.

스칼라에서는 주로 타입 클래스에 대한 구체적인 구현을 정의하고, Implicit 키워드를 사용하여 해당 인스턴스를 묵시적으로 사용하게 됩니다.

 

객체를 단순한 String으로 만들어 전송하도록 Message를 상속받은 PlainTextMessage를 작성하고, 전송할 객체인 User를 Message로 변하는 타입 인스턴스를 생성하는 예제는 다음과 같습니다 : 

final case class PlainTextMessage(body: String) extends Message

final case class User(name: String, mail: String)

object ListSerializer {
  implicit val UserWriter: Serializer[User] =
    new Serializer[User] {
      override def serialize(user: User): Message =
        PlainTextMessage(s"name:${user.name}, mail:${user.mail}")
    }
}

 

3. Type Class Interface

마지막으로 Type Class Interface는 사용자에게 노출하고자 하는 기능을 의미합니다. 인터페이스는 타입 클래스를 묵시적 파라미터로 받는 제네릭 메소드(Generic Method)입니다.

인터페이스를 작성하는 방법하는 일반적인 방법은 크게 두 가지가 있는데, 하나는 Interface Object를 지정하는 것이고, 다른 하나는 Interface Syntax를 작성하는 것입니다.

a. Interface Object

Interface Object는 메소드를 싱글톤 객체에 포함시켜 가장 간단하게 인터페이스를 만들 수 있는 방법입니다.

object Message {
  def toMessage[A](thing: A)(implicit s: Serializer[A]): Message = 
    s.serialize(thing)
}

 

이 객체를 사용하려면 정의해준 타입 인스턴스를 import하고 메소드를 호출하면 됩니다 : 

import ListSerializer._
Message.toMessage(User("Sean", "sean@email.com"))

 

실행 결과는 다음과 같습니다.

b. Interface Syntax

싱글톤 객체를 생성하는 대신에 implicit class를 사용하여 Message 트레이트를 상속받는 객체를 암시적으로 형변환하여 새로운 함수를 추가하여 사용하는 방법을 Cats에서는 Interface Syntax라 부르는데, 사용 예는 다음과 같습니다.

object ListSerializerSyntax {
  implicit class ListSerializerOps[A](thing: A) {
    def toMessage(implicit s: Serializer[A]): Message =
      s.serialize(thing)
  }
}

import ListSerializer._
import ListSerializerSyntax._
User("Sean", "sean@email.com").toMessage

 

실행 결과는 다음과 같습니다.

c. Implicitly

혹은 스칼라 표준 라이브러리에서 제공하는 제네릭 타입 클래스 인터페이스인 implicitly 메소드를 사용하는 방법도 있습니다. implicitly 메소드를 사용하게 되면 위와 같이 Interface Object 혹은 Interface Syntax를 작성해줄 필요 없이 Type Instance를 import한 다음, implicitly 메소드를 사용하여 impilcit 스코프에서 적절한 후보를 호출할 수 있도록 설정해 주면 모든 작업이 완료됩니다.

import ListSerializer._
def serialize(user: User): Message = implicitly[Serializer[User]].serialize(user)

serialize(User("Sean", "sean@email.com"))

 

실행 결과는 다음과 같습니다.

 

반응형

'Languages > Scala' 카테고리의 다른 글

[Scala Cats] 타입 공변성/반공변성 다루기  (0) 2020.12.15
[Scala Cats] Cats의 타입 클래스  (0) 2020.12.07
[스칼라] 함수 - 기초  (0) 2020.07.21
[스칼라] 반복문  (0) 2020.04.27
[스칼라] 매치 표현식  (0) 2020.03.23