如何使用猫和 State Monad
How to use cats and State Monad
我已经使用了 cats for the first time to solve day 1 代码的出现,我想知道是否有可能改进。
给定一个具有以下签名的方法 update
def update(i: Instruction): PosAndDir => PosAndDir
我想出了:
val state: State[PosAndDir, List[Unit]] = instructions.map(i => State.modify(update(i))).toList.sequenceU
val finalState = state.runS(PosAndDir(Pos(0, 0), North)).value
还有
def update2(i: Instruction): State[PosAndDir, Option[Pos]] =
State.modify(update(i)).inspect(pad => if (i == Walk) Some(pad.pos) else None)
…
val state = instructions.map(update2).toList.sequenceU
val positions = state.runA(PosAndDir(Pos(0, 0), North)).value.flatten
更准确地说,问题是:
- 为什么我们需要调用
.value
(使用 scalaz,它是透明的)?
- 有没有办法用 for 理解来编写
update2
以提高可读性?
- cats 中是否有
Seq
的 Applicative
实例(我知道 scalaz 中没有)。 ?
- 有改进代码的想法吗?
- cats 将
State[S, A]
定义为堆栈安全 StateT[Eval, S , A]
的别名,在 scalaz 术语中是 StateT[Trampoline, S, A]
,因此 runS
returns Eval[A]
,其中 value
将是 运行 即使对于很长的 flatMap
序列也没有计算器溢出。
使用更多的导入
import cats.data.{State, StateT}
import cats.MonadState
import cats.syntax.functorFilter._
import cats.instances.option._
和一些准备工作
type Walk[x] = StateT[Option, PosAndDir, x]
val stateMonad = MonadState[Walk, PosAndDir]
import stateMonad._
你可以让你的函数看起来像这样
def update2(i: Instruction): StateT[Option, PosAndDir, Pos] =
for (pad ← get if i == Walk) yield pad.pos
并非由于此 improvement,此解决方案无法在 2.12 中运行,您可以使用此解决方法使其运行
implicit class FunctorWithFilter[F[_] : FunctorFilter, A](fa: F[A]) {
def withFilter(f: A ⇒ Boolean) = fa.filter(f)
}
Seq
、this answer describes why. While there are some non-orthodox instances in the alleycats 项目没有实例。我不太确定你是否需要 Applicative[Seq]
,从你的代码中你更需要 Traverse[Seq]
,或者如果你将 sequence
替换为 sequence_
甚至 Foldable[Seq]
。
好消息 alleycats 中有 Foldable[Iterable]
,这是我尝试为 Seq
实例
定义相似的东西
implicit val seqInstance = new MonadFilter[Seq] with Traverse[Seq] {
def traverse[G[_] : Applicative, A, B](fa: Seq[A])(f: (A) ⇒ G[B]): G[Seq[B]] =
fa match {
case head +: tail ⇒ f(head).map2(traverse(tail)(f))(_ +: _)
case _empty ⇒ Seq.empty[B].pure[G]
}
def foldLeft[A, B](fa: Seq[A], b: B)(f: (B, A) ⇒ B): B = fa.foldLeft(b)(f)
def foldRight[A, B](fa: Seq[A], lb: Eval[B])(f: (A, Eval[B]) ⇒ Eval[B]): Eval[B] =
fa match {
case head +: tail ⇒ f(head, foldRight(tail, lb)(f))
case _empty ⇒ lb
}
def pure[A](x: A): Seq[A] = Seq(x)
def empty[A]: Seq[A] = Seq.empty[A]
def flatMap[A, B](fa: Seq[A])(f: (A) ⇒ Seq[B]): Seq[B] = fa.flatMap(f)
def tailRecM[A, B](a: A)(f: (A) ⇒ Seq[Either[A, B]]): Seq[B] = {
@tailrec def go(seq: Seq[Either[A, B]]): Seq[B] =
if (seq.contains((_: Either[A, B]).isLeft))
go(seq.flatMap {
case Left(a) ⇒ f(a)
case b ⇒ Seq(b)
}) else seq.collect { case Right(b) ⇒ b }
go(Seq(Left(a)))
}
override def mapFilter[A, B](fa: Seq[A])(f: (A) ⇒ Option[B]): Seq[B] =
fa.flatMap(f(_).toSeq)
}
没有花太多时间,但这是我尝试通过 Monocle library 来简化一些部分:
import cats.{MonadState, Foldable, Functor}
import cats.instances.option._
import cats.syntax.foldable._
import cats.syntax.functor._
import cats.syntax.functorFilter._
import monocle.macros.Lenses
@Lenses
case class Pos(x: Int, y: Int)
sealed abstract class Dir(val cmd: Pos ⇒ Pos)
case object South extends Dir(Pos.y.modify(_ - 1))
case object North extends Dir(Pos.y.modify(_ + 1))
case object East extends Dir(Pos.x.modify(_ + 1))
case object West extends Dir(Pos.x.modify(_ - 1))
@Lenses
case class PosAndDir(pos: Pos, dir: Dir)
val clockwise = Vector(North, East, South, West)
val right: Map[Dir, Dir] = clockwise.zip(clockwise.tail :+ clockwise.head).toMap
val left: Map[Dir, Dir] = right.map(_.swap)
sealed abstract class Instruction(val cmd: PosAndDir ⇒ PosAndDir)
case object TurnLeft extends Instruction(PosAndDir.dir.modify(left))
case object TurnRight extends Instruction(PosAndDir.dir.modify(right))
case object Walk extends Instruction(pd ⇒ PosAndDir.pos.modify(pd.dir.cmd)(pd))
def runInstructions[F[_] : Foldable : Functor](instructions: F[Instruction])(start: PosAndDir): PosAndDir =
instructions.map(i => State.modify(i.cmd)).sequence_.runS(start).value
我已经使用了 cats for the first time to solve day 1 代码的出现,我想知道是否有可能改进。
给定一个具有以下签名的方法 update
def update(i: Instruction): PosAndDir => PosAndDir
我想出了:
val state: State[PosAndDir, List[Unit]] = instructions.map(i => State.modify(update(i))).toList.sequenceU
val finalState = state.runS(PosAndDir(Pos(0, 0), North)).value
还有
def update2(i: Instruction): State[PosAndDir, Option[Pos]] =
State.modify(update(i)).inspect(pad => if (i == Walk) Some(pad.pos) else None)
…
val state = instructions.map(update2).toList.sequenceU
val positions = state.runA(PosAndDir(Pos(0, 0), North)).value.flatten
更准确地说,问题是:
- 为什么我们需要调用
.value
(使用 scalaz,它是透明的)? - 有没有办法用 for 理解来编写
update2
以提高可读性? - cats 中是否有
Seq
的Applicative
实例(我知道 scalaz 中没有)。 ? - 有改进代码的想法吗?
- cats 将
State[S, A]
定义为堆栈安全StateT[Eval, S , A]
的别名,在 scalaz 术语中是StateT[Trampoline, S, A]
,因此runS
returnsEval[A]
,其中value
将是 运行 即使对于很长的flatMap
序列也没有计算器溢出。 使用更多的导入
import cats.data.{State, StateT} import cats.MonadState import cats.syntax.functorFilter._ import cats.instances.option._
和一些准备工作
type Walk[x] = StateT[Option, PosAndDir, x] val stateMonad = MonadState[Walk, PosAndDir] import stateMonad._
你可以让你的函数看起来像这样
def update2(i: Instruction): StateT[Option, PosAndDir, Pos] = for (pad ← get if i == Walk) yield pad.pos
并非由于此 improvement,此解决方案无法在 2.12 中运行,您可以使用此解决方法使其运行
implicit class FunctorWithFilter[F[_] : FunctorFilter, A](fa: F[A]) { def withFilter(f: A ⇒ Boolean) = fa.filter(f) }
定义相似的东西Seq
、this answer describes why. While there are some non-orthodox instances in the alleycats 项目没有实例。我不太确定你是否需要Applicative[Seq]
,从你的代码中你更需要Traverse[Seq]
,或者如果你将sequence
替换为sequence_
甚至Foldable[Seq]
。 好消息 alleycats 中有Foldable[Iterable]
,这是我尝试为Seq
实例implicit val seqInstance = new MonadFilter[Seq] with Traverse[Seq] { def traverse[G[_] : Applicative, A, B](fa: Seq[A])(f: (A) ⇒ G[B]): G[Seq[B]] = fa match { case head +: tail ⇒ f(head).map2(traverse(tail)(f))(_ +: _) case _empty ⇒ Seq.empty[B].pure[G] } def foldLeft[A, B](fa: Seq[A], b: B)(f: (B, A) ⇒ B): B = fa.foldLeft(b)(f) def foldRight[A, B](fa: Seq[A], lb: Eval[B])(f: (A, Eval[B]) ⇒ Eval[B]): Eval[B] = fa match { case head +: tail ⇒ f(head, foldRight(tail, lb)(f)) case _empty ⇒ lb } def pure[A](x: A): Seq[A] = Seq(x) def empty[A]: Seq[A] = Seq.empty[A] def flatMap[A, B](fa: Seq[A])(f: (A) ⇒ Seq[B]): Seq[B] = fa.flatMap(f) def tailRecM[A, B](a: A)(f: (A) ⇒ Seq[Either[A, B]]): Seq[B] = { @tailrec def go(seq: Seq[Either[A, B]]): Seq[B] = if (seq.contains((_: Either[A, B]).isLeft)) go(seq.flatMap { case Left(a) ⇒ f(a) case b ⇒ Seq(b) }) else seq.collect { case Right(b) ⇒ b } go(Seq(Left(a))) } override def mapFilter[A, B](fa: Seq[A])(f: (A) ⇒ Option[B]): Seq[B] = fa.flatMap(f(_).toSeq) }
没有花太多时间,但这是我尝试通过 Monocle library 来简化一些部分:
import cats.{MonadState, Foldable, Functor} import cats.instances.option._ import cats.syntax.foldable._ import cats.syntax.functor._ import cats.syntax.functorFilter._ import monocle.macros.Lenses @Lenses case class Pos(x: Int, y: Int) sealed abstract class Dir(val cmd: Pos ⇒ Pos) case object South extends Dir(Pos.y.modify(_ - 1)) case object North extends Dir(Pos.y.modify(_ + 1)) case object East extends Dir(Pos.x.modify(_ + 1)) case object West extends Dir(Pos.x.modify(_ - 1)) @Lenses case class PosAndDir(pos: Pos, dir: Dir) val clockwise = Vector(North, East, South, West) val right: Map[Dir, Dir] = clockwise.zip(clockwise.tail :+ clockwise.head).toMap val left: Map[Dir, Dir] = right.map(_.swap) sealed abstract class Instruction(val cmd: PosAndDir ⇒ PosAndDir) case object TurnLeft extends Instruction(PosAndDir.dir.modify(left)) case object TurnRight extends Instruction(PosAndDir.dir.modify(right)) case object Walk extends Instruction(pd ⇒ PosAndDir.pos.modify(pd.dir.cmd)(pd)) def runInstructions[F[_] : Foldable : Functor](instructions: F[Instruction])(start: PosAndDir): PosAndDir = instructions.map(i => State.modify(i.cmd)).sequence_.runS(start).value