venerdì 1 aprile 2011

Monad state con scalaz

Da un po' di tempo studio scalaz. Una delle strutture dati più interessanti è State. Permette di mantenere lo stato degli oggetti tra le transizioni, ed è molto utile quando si vogliono concatenare delle operazioni in modo puramente funzionale, senza riassegnare le variabili.
Ad esempio, immaginiamo di avere un messaggio, con l'elenco dei destinatari a cui è stato inviato:
import java.util.Date

case class Message(
                   text : String,
                   sent : List[(String, Date)] = Nil
                  ){

  def sendTo(address : String) : Message =
    Message(text, (address, new Date) :: sent)

  override def toString = "Message " + text + 
         sent.map(s => s._1 + " on " + s._2)
             .mkString(" sent to ", ", to ", "")
}
Ora, se volessimo estendere la funzione sendTo in modo da prendere una lista di destinatari a cui inviare il messaggio, con State possiamo fare così:
import scalaz._
import Scalaz._

def sendTo(addresses : List[String]) 
           : State[Message, List[(String, Message)]] = 
   addresses match {
       case head :: tail => 
              for( s <- init[Message];
                   n <- modify[Message]( _ sendTo head ) ;
                   t <- sendTo(tail)
                 ) yield (head, s sendTo head) :: t
       case Nil => state(i => (i, Nil))
     }
Questa funzione prende in input la lista di destinatari e ritorna un oggetto che permette di effettuare l'operazione di inviare un messaggio a quei destinatari, ad esempio:
println( 
        sendTo(List("Onofrio", "Claudia")) 
              (Message("Hello World!")) 
       )
che restituisce una tupla contenente il messaggio finale più la lista di tutti i passaggi di stato:
(Message Hello World! 
 sent to Claudia on Fri Apr 01 23:21:49 CEST 2011, 
      to Onofrio on Fri Apr 01 23:21:49 CEST 2011,
List((Onofrio, Message Hello World! 
 sent to Onofrio on Fri Apr 01 23:21:49 CEST 2011), 
(Claudia, Message Hello World! 
 sent to Claudia on Fri Apr 01 23:21:49 CEST 2011, 
      to Onofrio on Fri Apr 01 23:21:49 CEST 2011)))
Ovviamente, il metodo è deve essere privo di side-effect, altrimenti, poiché viene eseguite più volte gli effetti sono imprevedibili. Per ottenere come side-effect l'invio di una e-mail, per esempio, da quanto ho capito è necessaria la monad Writer. Scriverò un post appena sarò riuscito a farlo con scalaz.

Nessun commento: