2016-12-31 89 views
5

我已經第一次使用cats來解決代碼的出現day 1,我想知道是否有可能改進的東西。如何使用貓和狀態Monad

給定一個方法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 

更準確地說,問題是:

  1. 爲什麼我們需要撥打.value(與斯卡拉,它是透明的)?
  2. 有沒有辦法寫update2用於理解提高可讀性?
  3. 是否有Applicative實例爲Seq在貓(我知道有沒有scalaz)。 ?
  4. 任何想法改善代碼?
+0

你能提供PosAndDir','Pos'和'Dir' – Odomontois

+0

肯定'定義。完整的代碼在這裏:https://gist.github.com/YannMoisan/18e44d8998d42d745a3ea9caaae4c16a –

回答

5
  1. State[S, A]定義爲一個別名堆棧安全StateT[Eval, S , A]這是StateT[Trampoline, S, A]在scalaz方面,所以runS回報Eval[A],其中value將沒有計算器,甚至很長時間flatMap序列運行。
  2. 使用一些額外的進口

    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 
    

    不是該解決方案將不會在2.12工作由於此improvement,您可以使用此解決方法

    implicit class FunctorWithFilter[F[_] : FunctorFilter, A](fa: F[A]) { 
        def withFilter(f: A ⇒ Boolean) = fa.filter(f) 
    } 
    
  3. 沒有爲Seq沒有實例,this answer介紹爲什麼。雖然alleycats項目中存在一些非正統情況。我不太確定您是否需要Applicative[Seq],從您的代碼中您需要Traverse[Seq],或者如果您將sequence替換爲sequence_,即使是Foldable[Seq]。 好消息出現在alleycatsFoldable[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) 
    } 
    
  4. 沒花了很多時間,但這裏是我試圖通過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 
    
+0

謝謝你指出我單片眼鏡。令人驚訝的是,你把'cmd'放在'Direction's上,而不是分離數據和行爲。爲什麼?下一個方向的預計算是聰明的。什麼是StateMonad? (我知道'State'是一個monad,'StateT'是一個monad變換器)。 PS:不要猶豫,我還有其他最近的問題在等待這樣的真棒答案... –