Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional pattern for double fold

Let the toy-class Counter such as:

class Counter private( val next: Int, val str2int: Map[String,Int] ) {
  def apply( str: String ): (Int,Counter)  = str2int get str match {
    case Some(i) => ( i, this )
    case None => ( next, new Counter( next+1, str2int + (str -> next) ) )
  }
}
object Counter {
  def apply() = new Counter( 0, Map() )
}

This class provides a mapping between a String and a natural number, the mapping is extended lazily each time a new String is queried.

I can then write a method which can convert a Seq of Strings in a Seq of Ints, updating the mapping during traversal. The first implementation I got is with foldLeft:

def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
  strs.foldLeft( (Seq[Int](), counter) ) { (result, str) =>
    val (i, nextCounter) = result._2( str )
    ( result._1 :+ i, nextCounter )
  }

This works as intended:

val ss = Seq( "foo", "bar", "baz", "foo", "baz" )
val is = toInt( ss, Counter() )._1
            //is == List(0, 1, 2, 0, 2)

But I am not very satisfied about toInt implementation. The problem is that I am folding on two different values. Is there a functional programming pattern to simplify the implementation ?

like image 986
paradigmatic Avatar asked Aug 24 '11 14:08

paradigmatic


3 Answers

The pattern you're looking for is the State monad:

import scalaz._
import Scalaz._

case class Counter(next: Int = 0, str2int: Map[String,Int] = Map()) {
  def apply( str: String ): (Counter, Int) = (str2int get str) fold (
    (this, _),
    (new Counter(next+1, str2int + (str -> next)), next)
  )}

type CounterState[A] = State[Counter, A]

def count(s: String): CounterState[Int] = state(_(s))

def toInt(strs: Seq[String]): CounterState[Seq[Int]] =
  strs.traverse[CounterState, Int](count)

The type annotation there is unfortunate, and maybe it can be eliminated somehow. Anyway, here's a run of it:

scala> val ss = Seq( "foo", "bar", "baz", "foo", "baz" )
ss: Seq[java.lang.String] = List(foo, bar, baz, foo, baz)

scala> val is = toInt(ss) ! Counter()
is: Seq[Int] = List(0, 1, 2, 0, 2)
like image 171
Apocalisp Avatar answered Oct 05 '22 09:10

Apocalisp


You can make the fold you've got look quite a bit nicer by doing more pattern matching:

strs.foldLeft((Seq[Int](), counter)) { case ((xs,counter), str) =>
  val (i, nextCounter) = counter(str)
  (xs :+ i, nextCounter)
}

And then if you have the pipe operator |> defined somewhere, and you're comfortable with the /: alias for foldLeft, you can make that

((Seq[Int](), counter) /: strs) { case ((xs,counter), str) =>
  counter(str) |> { case (i,nextCounter) => (xs +: i, nextCounter) }
}

which is, once you're familiar with the syntax, compact and readable.

like image 21
Rex Kerr Avatar answered Oct 05 '22 10:10

Rex Kerr


I think the state monad is what you're looking for.

like image 26
Jesper Nordenberg Avatar answered Oct 05 '22 10:10

Jesper Nordenberg