0

I'm trying to understand and get comfortable with the State Monad so I'm basically following along by copying a preexisting example. The 'State' is just a door that opens if it's Pushed when closed and closes when Pulled if it's open. If it's Pushed when open or Pulled when closed, it does nothing. So the code below seems to work as expected.

def main(args: Array[String]): Unit = {
import Monad._

// Push to open, pull to close
case class Door(open: Boolean = false)
trait Action
case object Push extends Action
case object Pull extends Action

def update: Action => Door => Door = action => door => {
  val curr = (action, door)
  println(curr)
  curr match {
    case (Push, Door(op)) => if (op) door else Door(!op)
    case (Pull, Door(op)) => if (op) Door(!op) else door
  }
}

val sm = stateMonad[Door] // Code for 'stateMonad' not shown

def simulate(inputs: List[Action]) = for {
  _ <- sm.sequence(inputs map (a => State.modify(update(a))))
  s <- State.get
} yield ()

val actions = List(Push, Pull, Pull, Push)

val r = simulate(actions).run(Door())

println(r) }

// The code above results in:

(Push,Door(false))
(Pull,Door(true))
(Pull,Door(false))
(Push,Door(false))
((),Door(true))

However, if I change the first case statement to this:

case (Push, Door(op)) => if (op) door else Door(false)

Which I would think would be the same thing... // It results in:

(Push,Door(false))
(Pull,Door(false))
(Pull,Door(false))
(Push,Door(false))
((),Door(false))

This has got to be something really stoopid on my part, but I can't find and I don't otherwise have an explanation for what is going on. Can someone please help?

Thanks.

  • 1
    You actually mixed up the meaning of Push and Pull in your code. Comments says that `// Push to open, pull to close`. Okay, You started with the door that is closed by default and after first iteration door should be opened (`Push to open`). – Artsiom Miklushou Oct 20 '15 at 19:25

1 Answers1

1

Like Artsiom mentioned in the comments, your are mixing Push/Pull and Door(true)/Door(false).

It might be easier to pattern match if the door is open or not :

curr match {
  case (Push, Door(false)) => Door(true)
  case (Pull, Door(true))  => Door(false)
  case _                   => door
}

Even more clear would be to add some methods to Door :

case class Door(isOpen: Boolean = false) {
  def isClosed = !isOpen
  def open = copy(isOpen = true)
  def close = copy(isOpen = false)
}

The pattern matching then could look like :

action match {
  case Push if door.isClosed => door.open
  case Pull if door.isOpen   => door.close
  case _                     => door
}

Checking with scalaz State :

import scalaz._, Scalaz._

def updateDoor(action: Action): State[Door, (Action, Door)] = 
  State(door => action match {
    case Push if door.isClosed => (door.open,  (action, door))
    case Pull if door.isOpen   => (door.close, (action, door))
    case _                     => (door,       (action, door))
  })

val actions = List(Push, Pull, Pull, Push) 
val (end, steps) = actions.traverseU(updateDoor).run(Door())

Which gives similar output to your first example :

scala> steps.foreach(println)
(Push,Door(false))
(Pull,Door(true))
(Pull,Door(false))
(Push,Door(false))

scala> println(end)
Door(true)
Peter Neyens
  • 9,770
  • 27
  • 33