哺乳類を実装する ~Cats入門~ #Day1(Type Class, Type Class Instances,Type Class Interfaces)

Day1

Scala with Catsを基本に進めていく

https://underscore.io/books/scala-with-cats/

環境としてsbtが入っており、下記コマンドでプロジェクトが作成されていることが前提

sbt new underscoreio/cats-seed.g8

1. Type Class, Type Class Instances,Type Class Interfaces

まずは基本の概念となるType Class, Type Class Instances,Type Class Interfaces(Interface Syntax)をおさえる.

Catsはこの3つをライブラリと提供しているため、必ず理解しなければならない.

1.1. Type Class

日本語名で型クラス

Scalaではcase classそのもののため理解に苦しむ人はいないだろう.

何かしらの情報を表示するtraitを作成してみる.

trait MyShow[A] {
  def show(value: A): String
}

1.2. Type Class Instances

型クラスインスタンス、上記の型クラスの実体となる.
実装手順は下記の通り.
1. Type Class Instancesを格納するobjectを作成
2. objectの中にMyShow[A]戻り値とする暗黙変数を定義, new MyShow[A]でshowを実装/インスタンスを作成する

哺乳類の体温を表示する型クラスインスタンスを実装してみよう

sealed trait Mammal{
    val temperature : Int
}
final case class Human(val temperature : Int = 36) extends Mammal
final case class Cat(val temperature : Int = 38) extends Mammal

object TempShowInstances {
  implicit val humanShow: MyShow[Human] =
    new MyShow[Human] {
      def show(value: Human): String = s"Human temperature is ${value.temperature}" 
    }

  implicit val catShow: MyShow[Cat] =
    new MyShow[Cat] {
      def show(value: Cat): String = s"Cat temperature is ${value.temperature}" 
    }
}

1.3. Type Class Interfaces

型クラスインターフェース、上記で作成したインスタンスへのアクセスインターフェース.

実装手順は下記の通り.
1. Type Class Interfacesを格納するobjectを作成
2. objectの中にMyShow[A]を暗黙引数とする関数を定義, 関数の中でMyShow[A]の関数にアクセス

哺乳類の体温を表示する型クラスインターフェースを実装してみよう

object Mammal {
  def showTemperature[A](value: A)(implicit w: MyShow[A]): String = w.show(value)
}

Type Class ~ Type Class Interfacesの実装で
実際に体温を表示することができるようになる.

Mammal.showTemperature(Human(35))
 > Human temperature is 35

1.4. Interface Syntax

1.3. まで体温を表示できるようになったが
実際には下記のようにHumanから直接体温を表示する関数を呼べるようにしたい

Human(35).showTemperature

これで使える機能がメソッド注入で
Scalaではimplicit classでメソッド注入を実現できる.

実装手順は下記の通り.

  1. Interface Syntaxを格納するobjectを作成
  2. objectの中に暗黙クラスを定義, クラスの中でMyShow[A]を引数とする関数を定義
object TempSyntax {
  implicit class TempShowOps[A](value: A) {
    def showTemperature(implicit w: MyShow[A]): String =
      w.show(value)
  }
}

オブジェクト指向との比較

1.1. ~ 1.4.をまとめると下記の通り.

sealed trait Mammal{
  val temperature : Int
}
final case class Human(val temperature : Int = 36) extends Mammal
final case class Cat(val temperature : Int = 38) extends Mammal

object Mammal {
  def showTemperature[A](value: A)(implicit w: MyShow[A]): String = w.show(value)
}

trait MyShow[A] {
  def show(value: A): String
}

object TempShowInstances {
  implicit val humanShow: MyShow[Human] =
    new MyShow[Human] {
      def show(value: Human): String = s"Human temperature is ${value.temperature}"
    }

  implicit val catShow: MyShow[Cat] =
    new MyShow[Cat] {
      def show(value: Cat): String = s"Cat temperature is ${value.temperature}"
    }
}

object TempSyntax {
  implicit class TempShowOps[A](value: A) {
    def showTemperature(implicit w: MyShow[A]): String =
      w.show(value)
  }
}

これをオブジェクト指向で書いてみるとどうなるだろうか?

sealed trait Mammal{
  val name : String
  val temperature : Int
  def showTemperature = s"${name} temperature is ${temperature}"
}
final case class Human(val temperature : Int = 36) extends Mammal{
    val name = "human"
}
final case class Cat(val temperature : Int = 38) extends Mammal{
    val name = "cat"
}

あれ、こっちのがスッキリしているぞ…

オブジェクト指向は汎化と特化して継承・継承・・・共通部分を見出す作業

関数型は有る集合に関して関数定義・関数定義・・・新しい関数追加していく作業

モデル定義してからオブジェクト指向で書くと問題ないが、
どんどん肥大化していくような状況だと関数型のがスケールする.

一長一短だ.

編集後記

implicitでなにか書く時REPLがすごく使いにくい….

Cats, ScalaCats, Scala

Posted by RV