Skip to content

Instantly share code, notes, and snippets.

@halcat0x15a
Last active January 4, 2016 22:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save halcat0x15a/8686076 to your computer and use it in GitHub Desktop.
Save halcat0x15a/8686076 to your computer and use it in GitHub Desktop.

!SLIDE

Coyoneda + Free

halcat0x15a

!SLIDE

Coyoneda

任意のF[_]から作れる

case class Container[A](value: A)

val zero = Coyoneda(Container(0))

!SLIDE

CoyonedaはFunctor

val one = zero.map(_ + 1)

!SLIDE

つまり任意のF[_]がFunctorになるヤッター!?

!SLIDE

Functor

これは種も仕掛けもある

abstract class Coyoneda[F[_], A] {
  type I
  def fi: F[I]
  def k(i: I): A
  def run(implicit F: Functor[F]): F[A] = F.map(fi)(k)
}

!SLIDE

結局F[_]を取り出すときにFunctorが要るじゃないですかヤダー

〜完〜

!SLIDE

Free

任意のFunctor[F]からMonad[F]が作れる。

case class Pair[A](left: A, right: A)
object Pair {
  implicit def functor = new Functor[Pair] {
    def map[A, B](pair: Pair[A])(f: A => B): Pair[B] =
      Pair(f(pair.left), f(pair.right))
  }
}
type BinaryTree[A] = Free[Pair, A]
val btree = Free.Suspend[Pair, Int](Pair(Free.Suspend[Pair, Int](Pair(Free.Return(0), Free.Return(1))), Free.Return(2)))
val result = for {
  a <- btree
  b <- btree
} yield (a, b)
result.runM { case Pair(l, r) => List(l, r) } assert_=== List((0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2))

!SLIDE

つまりCoyonedaと合わせれば任意のF[_]がMonadになるヤッター!?

!SLIDE

Sample

Emulator Monad

State+Maybe+IO

!SLIDE

sealed trait Action[A] {

  def apply(state: State): Result[A]

}

!SLIDE

case class State(name: String, config: Config, updates: List[Option[Date]])

sealed trait Result[+A]

case class Success[A](value: A, state: State) extends Result[A]

case object Failure extends Result[Nothing]

!SLIDE

case object Read extends Action[State] {

  def apply(state: State) = Success(state, state)

}

case class Write(state: State) extends Action[Unit] {

  def apply(s: State) = Success((), state)

}

!SLIDE

case object Capture extends Action[BufferedImage] {

  def apply(state: State) = Success(Emulator.robot(state.config).createScreenCapture(Emulator.bounds), state)

}

!SLIDE

case class Find(image: BufferedImage, screen: BufferedImage) extends Action[Point] {

  def apply(state: State) = {
    val points = for {
      points <- for {
        ax <- 0.to(screen.getWidth - image.getWidth).view
        ay <- 0.to(screen.getHeight - image.getHeight).view
      } yield for {
        bx <- 0.until(image.getWidth).view
        by <- 0.until(image.getHeight).view
      } yield (ax, ay) -> (bx, by)
      if points forall {
        case ((ax, ay), (bx, by)) =>
          screen.getRGB(ax + bx, ay + by) == image.getRGB(bx, by)
      }
      ((x, y), _) <- points
    } yield (x + image.getWidth / 2, y + image.getHeight / 2)
    points.headOption.map(Success(_, state)) getOrElse Failure
  }

}

!SLIDE

case class Click(point: Point) extends Action[Unit] {

  def apply(state: State) = {
    val robot = Emulator.robot(state.config)
    val (x, y) = point
    import robot._
    mouseMove((Emulator.bounds.getWidth / 2).toInt, (Emulator.bounds.getHeight / 2).toInt)
    mouseMove(x, y)
    mousePress(InputEvent.BUTTON1_MASK)
    mouseRelease(InputEvent.BUTTON1_MASK)
    mouseMove(0, 0)
    Success((), state)
  }

}

!SLIDE

  type Program[A] = Free.FreeC[Action, A]

  type CoyoAction[A] = Coyoneda[Action, A]

  implicit def monad = Free.freeMonad[CoyoAction]

  implicit def lift[A](action: Action[A]) = Free.liftF[CoyoAction, A](Coyoneda(action))

!SLIDE

  def run[A](program: Program[A], state: State, default: => A): A =
    program.resume match {
      case -\/(coyo) =>
        coyo.fi(state) match {
          case Success(value, state) => run(coyo.k(value), state, default)
          case Failure => default
        }
      case \/-(value) => value
    }

!SLIDE

  def find(image: BufferedImage) =
    for {
      screen <- Capture
      point <- Find(image, screen)
    } yield point

  def click(point: Point): Program[Unit] = Click(point)
  def click(image: BufferedImage): Program[Unit] =
    for {
      point <- find(image)
      _ <- Click(point)
    } yield ()

!SLIDE

  def or[A](a: Program[A], b: => Program[A]) =
    new Action[A] {
      def apply(state: State) =
        run(a.map(Success(_, state): Result[A]), state, run(b.map(Success(_, state): Result[A]), state, Failure))
    }

!SLIDE

雑感

結局runでいろいろ書く。

runの書き方が複数あるので場合によっては楽に書ける。(runM, foldMap, foldRun)

Monadを直接書くよりは楽?

Freeが難しい。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment