Help with using the State Monad elegantly (x post from scala-user)

123 views
Skip to first unread message

Vincent Marquez

unread,
Nov 16, 2012, 6:44:36 PM11/16/12
to scala-fu...@googlegroups.com
I'm playing around with Scalaz' State class, seems very nice for certain use cases, however I'm not sure i'm using it in the 'correct' way, if not an elegant way. 

In my example I have a List[Int], and I simply want to generate a new list, depending on certain criteria I will execute different transformations that also return state, along with recursing till the end of the list. 

 Rather than having to deal with passing tuples around and all that jazz, using State is much nicer!  However, it is a bit weird that i'm calling 'apply' inside something that returns state to recurse.  

I feel like i'm probably missing something basic, so advice would be appreciated!  I do realize there are easier ways to 'map' a list, this just seemed like a good exercise that could represent more complex data structures.   Any suggestions appreciated, thanks in advance!

TL;DR: Code is working as intended, need help making prettier.  :-)

--Vincent






object StateMonadExample {


  def main(args: Array[String]) {

  val startlist = List(1,2,3,4,5)

  val finalstate = stateParse 

  val resultingList = finalstate ! startlist

  println(resultingList)

  if(resultingList == List(-1, 3, -3, 5, -5)) 

       println("success")

  else 

       println("fail!")

}

def stateParse: State[List[Int], List[Int]] = state[List[Int], List[Int]]( list => {

list.headOption match {

  case None =>

    (list, List[Int]()) //lets not infinitely recurse

   

  case Some(i:Int) if (i%2 == 0) =>

    (for(a <- pullAndAdd;

        b <- stateParse)

        yield(a :: b))(list)

       

  case _ =>

    (for(a <- pullAndInvert;

        b <- stateParse)

    yield (a :: b))(list)

}

})

    def pullAndInvert = state[List[Int], Int](li => (li.tail, -li.headOption.getOrElse(0)))

    

    def pullAndAdd = state[List[Int], Int](li => (li.tail, 1+li.headOption.getOrElse(0)))

    

    /* copied from Scalaz for convenience, this is not my code, it's from Scalaz */


    sealed trait State[S, +A] {

    def apply(s: S): (S, A)


    def state[S, A](f: S => (S, A)): State[S, A] = new State[S, A] {

        def apply(s: S) = f(s)

    }

   

    def map[B](f: A => B): State[S, B] = state(apply(_) match {

        case (s, a) => (s, f(a))

    })


    def flatMap[B](f: A => State[S, B]): State[S, B] = state(apply(_) match {

        case (s, a) => f(a)(s)

    })


    def !(s: S): A = apply(s)._2


    def ~>(s: S): S = apply(s)._1


    def withs(f: S => S): State[S, A] = state(f andThen (apply(_)))

    }

    

    def state[S, A](f: S => (S, A)): State[S, A] = new State[S, A] {

        def apply(s: S) = f(s)

    }


}

Tony Morris

unread,
Nov 16, 2012, 7:03:25 PM11/16/12
to scala-fu...@googlegroups.com
As it happens, I recently created some exercises around the state monad.

In particular, remove all duplicates from a list and find the first
repeating element in a list (elements have natural ordering in both cases).
https://github.com/tonymorris/course/blob/answers/src/L03/State.scala
--
Tony Morris
http://tmorris.net/

Vincent Marquez

unread,
Nov 20, 2012, 6:26:12 PM11/20/12
to scala-fu...@googlegroups.com
Thanks Tony.  I do see you are calling apply to recurse in some of the code samples, so it's good to know I'm not totally off in the wrong direction by doing that. 

I whipped up another quick method that uses State to recurse through XMLEvents and turn it into hierarchical xml.Node objects.  If anyone has comments on my code, it's always appreciated.   At the very least, is this an appropriate use case for State?  

--Vincent 
import scala.xml._
import scala.xml.pull._
import scala.io.Source

object EventToNodeParser {
  
	def main(args:Array[String]) {
	  
		val str = "<tag attr='somestuff'><sub:subtag><mostnested>SomeText</mostnested></sub:subtag><secondsubtag attr='blah'></secondsubtag><thirdsubtab></thirdsubtab></tag>"
		
		var events = List[XMLEvent]()
		val reader = new XMLEventReader(Source.fromString(str))
		
		while (reader.hasNext) {
			events = events :+ reader.next
		}
		println( "events = " + events)
		
		val i = parseXMLEvents ! events
		  
		println("i = " + i)
	}
  
	
	def parseXMLEvents: State[List[XMLEvent], List[Node]] = state[List[XMLEvent], List[Node]]( list => {
    	list.headOption match {
    	  	case None =>
    	  		(list, List[Node]())
    	  		
    	  	case Some(EvText(txt))=>
    	  		(list.tail, List[Node](Text(txt)))
    	  		
    	  	case Some(EvElemStart(prefix,label,attr, scope)) =>
    	  		(for 
    	  		{ 
    	  		    children <- getChildren(label)
    	  		    nodes <- parseXMLEvents
    	  		}
    	  		    yield (new Elem(prefix, label, attr, scope, children:_*) :: nodes))(list.tail)
    	  	
    	  	case _ =>
    	  		parseXMLEvents(list.tail)
    	}
    })
    
    def getChildren(endLabel: String) = state[List[XMLEvent], List[Node]](list => {
    	val (sublist, newlist) = list.span( _ match {
    	  case EvElemEnd(_,label) if (label == endLabel)=>
    	    false
    	  case _ => true
    	})
    	sublist.headOption match {
    	  	case Some(e:XMLEvent) =>
    	  	  	(newlist, parseXMLEvents ! sublist)
    	  	case _ =>
    	  	  	(list, List[Node]())
    	}
    })
    
    /* copied from Scalaz for convenience */

    sealed trait State[S, +A] {
    def apply(s: S): (S, A)

    def state[S, A](f: S => (S, A)): State[S, A] = new State[S, A] {
        def apply(s: S) = f(s)
    }
   
    def map[B](f: A => B): State[S, B] = state(apply(_) match {
        case (s, a) => (s, f(a))
    })

    def flatMap[B](f: A => State[S, B]): State[S, B] = state(apply(_) match {
        case (s, a) => f(a)(s)
    })

    def !(s: S): A = apply(s)._2

    def ~>(s: S): S = apply(s)._1

    def withs(f: S => S): State[S, A] = state(f andThen (apply(_)))
    }
    
    def state[S, A](f: S => (S, A)): State[S, A] = new State[S, A] {
        def apply(s: S) = f(s)
    }

}


--
You received this message because you are subscribed to the Google Groups "scala-functional" group.
To unsubscribe from this group, send email to scala-function...@googlegroups.com.



Reply all
Reply to author
Forward
0 new messages