domenica 28 ottobre 2012

Android: fragments e UI designers

Ultimamente lavoro molto in Android, a contatto con UI designers molto bravi e meno bravi.

Premetto che sono d'accordo con questo celebre post di Jeff Atwood. Però lasciando fare tutto ai designers si rischia l'opposto: se non conosce le opportunità offerte della tecnologia, il designer potrebbe fare assunzioni sbagliate sulla possibilità di certe soluzioni o semplicemente ignorarle. Inoltre sappiamo bene come sia facile in Italia improvvisarsi in qualsiasi ruolo, soprattutto quando va di moda.

Ad esempio un designer sprovveduto potrebbe credere che un'applicazione Android dovrebbe essere uguale per tablet e smartphone, per risparmiare sullo sviluppo.
E allora via con bellissimi bottoni immensi senza testo al centro! Lo store è pieno di applicazioni che per un tablet da 10 pollici diventano così:

Questo magari perché il designer non sapeva che in Android i fragments permettono di riutilizzare le logiche del programma in modo del tutto trasparente rispetto al layout scelto per il dispositivo e ha scartato la possibilità di mostrare, per i tablet, il contenuto direttamente nell'activity principale.

martedì 29 maggio 2012

Introduzione a Flag Driven Development

Credo che ad ogni sviluppatore sia capitato di lavorare con questo approccio, dalle dinamiche molto interessanti. Generalmente un prodotto o una una logica in un software comincia con un algoritmo del tipo:

per ogni x tale che x appartiene all'insieme A
esegui l'operazione f(x)


Inizialmente va tutto bene e tutti sono entusiasti del prodotto. Inevitabilmente qualcuno fa notare che così com'è non va bene, e bisognerebbe distinguere una situazione in cui l'operazione su x non va fatta.

La soluzione a questo problema, secondo l'approccio Flag Driven Development è: aggiungere un flag su x. Il nostro algoritmo diventa:

per ogni x tale che x appartiene all'insieme A 
   se x.flag = falso
      esegui l'operazione f(x)

Qualche volta il flag indica che bisogna eseguire un'altra funzione:



per ogni x tale che x appartiene all'insieme A 
   se x.flag = falso
      esegui l'operazione f(x)
   altrimenti
      esegui l'operazione g(x)


Come si vede, l'approccio è incrementale perché è sempre possibile aggiungere il flag del flag e rendere il sistema più potente:


per ogni x tale che x appartiene all'insieme A 
   se x.flag1 = falso
      se x.flag2 = falso
         esegui l'operazione f(x)
      altrimenti
         esegui l'operazione h(x)
   altrimenti
Bandiera del Regno d'Italia Napoleonico
      esegui l'operazione g(x)


Si può evitare il flag? Sì, ma perché dovremmo? Questo approccio è un obbrobrio? Anche secondo me, ciò non toglie che è sempre molto usato.
Nei prossimi post forse ritornerò sull'argomento per esplicitare meglio certe dinamiche o forse no.

sabato 5 novembre 2011

Machine Learning e Scala al JugMarche 2

Pubblico le slide del meeting:

... e ringrazio tutti i partecipanti. Il codice delle demo sarà ufficialmente pubblicato su github ASAP :)


Stay tuned!

sabato 15 ottobre 2011

Machine Learning e Scala al JUG Marche

Sabato 29 ottobre terrò un talk di un'ora circa al Jug Marche a Falconara Marittima (AN) trattando di usi pratici di Machine Learning ed esempi in Scala. Maggiori dettagli sull'evento ed iscrizione(gratuita) qui.

venerdì 9 settembre 2011

Scala-recog accettato da scala-tools

Per me la buona notizia della settimana è che scala-recog è stato accettato da scala-tools quindi sarà scaricabile e importabile nei progetti scala con sbt, dallo stesso repository che contiene nientepopodimeno che scalaz, scalala, Lift, etc. etc.

sabato 6 agosto 2011

scala-recog: flessibilità con logistic regression

In questa wiki page ho messo qualche esempio sulla flessibilità che si può raggiungere usando le stackable modification.

In realtà con la logistic regression con scala-recog era per ottenere più flessibilità senza rinunciare alle ottimizzazioni necessarie per un algoritmo di training.

Ma quel che vorrei raggiungere raggiunge uno sforzo estra di desgin. Mi piacerebbe una libreria molto generale, indipendente dal dominio di applicazione.

Per esempio mi piacerebbe poter scrivere qualcosa tipo:

import org.scalarecog.logisticregression.SigmoidClassifier._  
val myStuff = List( 
                Stuff(3, 2.1, "just"), 
                Stuff(2, 1.1, "an"), 
                Stuff(3, 1.2, "example") 
              )  
val trainingSet = myStuff toTrainingSet ( 
                   _.number, _.number2), _.classLabel 
                  )           
val classifier = trainingSet.train

Ma mi piacerebbe anche raggiungere questo tipo di flessibilità:

val classifier = ( 
    for(stuff <- trainingSet; writer <- stuff.toWriter ) 
  ) train  

(classifier.log) foreach(println(_))

Forse si può prevedendo un supporto per le monadi nei training set. Un esempio di monade può essere questo di Tony Morris.

venerdì 29 luglio 2011

Alberi di decisione con scala-recog

scala-recog è un progetto open-source che ho creato su Google Code, una libreria di algoritmi di machine learning totalmente scritta in Scala. Uno di questi è ID3, un algoritmo che permette di costruire un albero di decisione basandosi su un training set di elementi già classificati.

Esempio: classificare curricula

Supponiamo di avere un sito per la ricerca di lavoro e vogliamo classificare i curricula inseriti per mostrarli agli utenti che cercano delle determinate figure. Dai curricula si possono estrarre una enomre quantità di dati ma supponiamo di voler estrarre solo questi:
  • se il candidato ha delle certificazioni
  • se è una persona loquace
  • se è iscritto al golf club
  • se ha una laurea o un master
e mettiamo tutto in una classe:
  case class Person(
                val hasCertifications : Boolean,
                val isTalkative : Boolean,
                val golfClub : Boolean,
                val hasMasterDegree : Boolean,
                val job : String
               )

  val persons = Person(hasCertifications = true, isTalkative = false, 
                       golfClub = false, hasMasterDegree = true, 
                       job = "Programmer") ::
                Person(hasCertifications = false, isTalkative = false, 
                       golfClub = false, hasMasterDegree = true, 
                       job = "Junior Programmer") ::
                Person(hasCertifications = true, isTalkative = false, 
                       golfClub = false, hasMasterDegree = false, 
                       job = "Programmer") ::
                Person(hasCertifications = false, isTalkative = true, 
                       golfClub = false, hasMasterDegree = true, 
                       job = "Seller") ::
                Person(hasCertifications = false, isTalkative = true, 
                       golfClub = false, hasMasterDegree = false, 
                       job = "Seller") ::
                Person(hasCertifications = true, isTalkative = true, 
                       golfClub = false, hasMasterDegree = false, 
                       job = "Seller") ::
                Person(hasCertifications = false, isTalkative = true, 
                       golfClub = true, hasMasterDegree = true, 
                       job = "CEO") ::
                Person(hasCertifications = false, isTalkative = false, 
                       golfClub = true, hasMasterDegree = false, 
                       job = "CEO") ::
                Person(hasCertifications = false, isTalkative = false, 
                       golfClub = true, hasMasterDegree = false, 
                       job = "CEO") ::
                Nil
Nella lista persons ho messo il mio training set. Per usare l'algoritmo ID3, basta importare l'oggetto giusto e usare la lista come training set:
import org.scalarecog.decisiontree._

def toVector(p : Person) = Vector(p.hasCertifications, p.isTalkative, p.golfClub, p.hasMasterDegree)
val dataset = persons map (p => (toVector(p), p.job))

val tree = new ID3[Boolean,String] buildTree dataset
La funzione toVector server perché la classe ID3 ha bisogno di un Vector.
Ora tree può classificare una nuova persona:
val newPerson = Person(false, false, false, false, "?")
assert(
   tree.classify(toVector(newPerson)) == "Junior Programmer"
)
Ma sarebbe carino visualizzare l'albero di decisione creato da ID3. Con JGraph è semplicissimo e otteniamo questo:
Ehi! È proprio l'algoritmo usato nella vita reale! ^_^
Ecco qui il codice completo:

package scalarecoggraph

import org.scalarecog.decisiontree._
import javax.swing.JFrame
import com.mxgraph.swing.mxGraphComponent
import com.mxgraph.view.mxGraph

class Program(tree : DecisionTree[Vector[Boolean], String], propertyNames : Vector[String]) extends JFrame("ScalaRecog") {
  type Tree = DecisionTree[Vector[Boolean], String]
  type Vertex = (AnyRef, (Double, Double))

  draw()

  def draw() {
    val graph: mxGraph = new mxGraph
    val root = graph.getDefaultParent

    def draw(t : Tree, parentPos : (Double, Double), offset : (Int, Int)) : Vertex = {
      def createVertex(label : String, action : Vertex => Unit = v => {}) : Vertex = {
        val vertexSize = (100, 30)
        val newPos = (parentPos._1 + offset._1, parentPos._2 + offset._2)
        val created = (graph.insertVertex(root, null, label, newPos._1, newPos._2 , vertexSize._1, vertexSize._2), newPos)
        action(created)
        created
      }
      def createEdge(label : String, from : Vertex, to : Vertex) = graph.insertEdge(root, null, label, from._1, to._1)

      t match {
        case a : DecisionLeaf[Vector[Boolean],String] => createVertex(a.label)
        case a : DecisionBranchVector[String,Boolean] =>
          createVertex(propertyNames(a.index), n => {
            for (  ((label, child), index) <- a.branches.zipWithIndex  )
              createEdge(label.toString, n, draw(child, n._2, (120*index, offset._2)))
            })
      }
    }

    graph.getModel.beginUpdate
    try {
      draw(tree, (0, 0), (120, 120))
    }
    finally {
      graph.getModel.endUpdate
    }
    getContentPane.add(new mxGraphComponent(graph))
  }
}

object Program  {

  case class Person(
                val hasCertifications : Boolean,
                val isTalkative : Boolean,
                val golfClub : Boolean,
                val hasMasterDegree : Boolean,
                val job : String
               )

  def main(args : Array[String]) : Unit = {

    val persons = Person(hasCertifications = true, isTalkative = false, golfClub = false, hasMasterDegree = true, job = "Programmer") ::
                  Person(hasCertifications = false, isTalkative = false, golfClub = false, hasMasterDegree = true, job = "Junior Programmer") ::
                  Person(hasCertifications = true, isTalkative = false, golfClub = false, hasMasterDegree = false, job = "Programmer") ::
                  Person(hasCertifications = false, isTalkative = true, golfClub = false, hasMasterDegree = true, job = "Seller") ::
                  Person(hasCertifications = false, isTalkative = true, golfClub = false, hasMasterDegree = false, job = "Seller") ::
                  Person(hasCertifications = true, isTalkative = true, golfClub = false, hasMasterDegree = false, job = "Seller") ::
                  Person(hasCertifications = false, isTalkative = true, golfClub = true, hasMasterDegree = true, job = "CEO") ::
                  Person(hasCertifications = false, isTalkative = false, golfClub = true, hasMasterDegree = false, job = "CEO") ::
                  Person(hasCertifications = false, isTalkative = false, golfClub = true, hasMasterDegree = false, job = "CEO") ::
                  Nil

    def toVector(p : Person) = Vector(p.hasCertifications, p.isTalkative, p.golfClub, p.hasMasterDegree)
    val dataset = persons map (p => (toVector(p), p.job))

    val tree = new ID3[Boolean,String] buildTree dataset

    val newPerson = Person(false, false, false, false, "?")
    assert(
      tree.classify(toVector(newPerson)) == "Junior Programmer"
    )

    val frame = new Program(tree, Vector("Has certifications?", "Is talkative?", "Likes playing golf?", "Has a master degree?"))
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    frame.setSize(400, 320)
    frame.setVisible(true)
  }
}

domenica 17 luglio 2011

algoritmo k-NN: meglio in Scala

Stavo leggendo questo libro su machine learning. È un buon libro ma mi ero stufato della verbosità di Python, con cui sono presentati gli algoritmi, quindi ho scritto un'implementazione dell'algoritmo k-NN in Scala:
package knn

class Knn[TData, TClass,
          TDistance <% Ordered[TDistance] ]
(
  val k : Int,
  val dataset : List[TData],
  val getClassOfData : TData => TClass,
  val getDistance : (TData, TData) => TDistance
)
{
  if(k < 1)
    throw new scala.IllegalArgumentException("k must be positive")

  def classify(data : TData) : TClass = {

    // Compute distances between tested data and dataset items
    val distances = for( d <- dataset ) yield (d, getDistance(d, data))

    // sort data by distance
    val sorted = distances sortBy ( d => d._2 )

    // take first k items
    val firstk = sorted take k

    // group by labels
    val classes = firstk groupBy (d => getClassOfData(d._1))

    // get most frequent label
    val classification = classes maxBy (g => g._2.length)

    classification._1
  }
}
Wow, ho scritto questa classe in dieci minuti funziona con ogni tipo di dataset dove sia definita una distanza e ovviamente una classificazione, come si può vedere con questo test (scritto con ScalaTest):
package knn

import org.scalatest._
import org.scalatest.matchers._
import scala.math._

class KnnTest extends FlatSpec with ShouldMatchers {

  "2nn" should " classify correctly with a sample near two well-known labelled items" in {
    val alg = new Knn(2,
          List((3, "ok"), (18, "ok"), (21, "high"), (64, "high")),
          (i : (Int, String)) => i._2,
          (i1: (Int, String), i2: (Int, String)) => abs(i1._1 - i2._1))

    val classified = alg.classify((4, ""))

    classified should equal ("ok")
  }
}
Naturalmente il codice può essere ottimizzato e generalizzato ma mostra come si possa scrivere codice veramente disaccoppiato e generale in Scala.

giovedì 14 luglio 2011

Aboliamo static

Su twitter ho proposto scherzosamente di abolire la parola chiave static dei linguaggi garbage collected. All'inizio era appunto uno scherzo, poi mi sono detto:

posso effettivamente fare a meno di static?
Innanzitutto, perché fare a meno di static? Perché può dare un sacco di problemi.

Factory

Prendiamo una classe e un'innocente metodo static per costruirla:


public class DumbClass {

  private String foo;
  public String getFoo() { return foo; }
  public void setFoo(String foo) { this.foo = foo; }

  private String initializedStuff;
  public String getInitializedStuff() { return initializedStuff; }

  private DumbClass(String foo, String initializedStuff) {
    this.initializedStuff = initializedStuff;
    this.foo = foo;
  }

  public static DumbClass create(Context c) {
    return new DumbClass( c.doSomethingToGetFoo(), c.doSomethingToGetStuff() );
  }
}

Inevitabilmente, come dice Alan Shalloway, le specifiche cambiano e uno dei vostri clienti vuole che, solo per lui, foo sia in CamelCase piuttosto che tutto MAIUSCOLO. E voi:
Facile: mettiamo in una libreria (che diamo solo a lui) una classe che estend.. Ops: non si può estendere, è un metodo statico! E se invece avessi fatto un'abstract factory?

public class DumbClass {

  private String foo;
  public String getFoo() { return foo; }
  public void setFoo(String foo) { this.foo = foo; }

  private String initializedStuff;
  public String getInitializedStuff() { return initializedStuff; }

  DumbClass(String foo, String initializedStuff) {
    this.initializedStuff = initializedStuff;
    this.foo = foo;
  }
}

public interface IDumbClassFactory {
  DumbClass create(Context c);
}

public class DefaultDumbClassFactory {
  public DumbClass create(Context c) {
    return new DumbClass( c.doSomethingToGetFoo(), c.doSomethingToGetStuff() );
  }
}

Allora basterebbe wrappare DefaultDumbClassFactory o fare un'altra implementazione di IDumbClassFactory e avrei fatto!
Morale: non usare metodi statici, meno che mai pubblici!

Campi privati

A volte capita di voler tenersi un contatore di istanze di una certa classe per sapere quante ce ne sono in giro. E si fa con un bel campo static:


public class Foo {

  private static int howMany;

  public Foo(Something s) {
    howMany++;
  }
  ...
}

Risultato? Scoprite che in realtà quel contatore non lo legge mai nessuno, anzi, se usate la libreria su un web server che gira per 2 anni di fila, alla fine vi va in overflow e causa un eccezione che ovviamente non gestite perché non ci avevate pensato. Oppure vorrete che non tenga traccia delle classi figlie, oppure per qualcuna sì e qualcuna no, e allora? Scoprite che vi serviva un'abstract factory.
Morale: quando sto per scrivere static ci penso due volte e quasi sempre alla fine scrivo: interface

Wolfsburg VW-Werk

mercoledì 13 luglio 2011

Secondo articolo su Scala

Nel mio secondo articolo introduttivo su Scala affronto una questione secondo me cruciale per Scala, ossia la sue grandi potenzialità nella manipolazione di collezioni.

Segnalo anche il bel resoconto del Better Software 2011 di Francesco Saliola, interessante e tanto più utile per chi come me non ha potuto partecipare.

martedì 21 giugno 2011

Option è un container (e una monade)!

Supponiamo di avere una lista di Option:
val myList = List( 
                   Some(1), 
                   Some(4), 
                   None, 
                   Some(5) 
                  )
e volete solo i valori, quindi List(1, 4, 5). Qualche volta trovate codice analogo al seguente, magari scritto da me l'anno scorso :)
val values = myList filter (! _.isEmpty) map (_.get)
assert( values == List(1, 4, 5) )
Orribile!
Infatti, essendo Option un container possiamo (e dobbiamo) fare questo:
val values = myList.flatten
assert( values == List(1, 4, 5) )
o, al massimo:
val values = for( 
                 Some(a) <- myList 
                ) yield a

giovedì 28 aprile 2011

Da Scala a LINQ

Spesso, durante il mio lavoro diurno (C# e Javascript) mi trovo a rimpiangere gli strumenti di cui dispongo nel mio lavoro notturno (Scala e Java).

Stavolta è andata così. Mi trovavo spesso a scrivere codice non transazionale in cui devo fare un'operazione su una serie di oggetti ma le eccezioni dovevano essere gestite ed aggregate, in questo modo:


var erroriGestiti = new List<QualcheCosa>();

foreach( var item in items ) {
   try {
      FaiQualcosaCon(item);
   }
   catch( QualcheTipoDiEccezione excp) {
      a.Add( CreaQualcheCosaDa(excp) );      
   }
}

return erroriGestiti;
Vigili del Fuoco (Vatican) - fleet Ed ero ovviamente stufo di ripetere questo costrutto. Ora, in Scala astrarre il concetto "collezionare le eccezioni di operazioni in sequenza" è un gioco da ragazzi, per cui lo tralascio. Però, anche se dire che LINQ ti permette di fingere di essere in Scala sarebbe un'assurdità (neanche F# te lo fa dire), può darti una mano:

public static 
IEnumerable<TReturn> CollectErrors<TItem, TException, TReturn>(
       this IEnumerable<TItem> items, 
       Action<TItem> act, 
       Converter<TException, TReturn> exceptionHandler
)
      where TException : Exception
{
   if (items == null) 
       throw new ArgumentNullException("items");
   if (act == null) 
       throw new ArgumentNullException("act");
   if (exceptionHandler == null) 
       throw new ArgumentNullException("exceptionHandler");

   return items
        .Aggregate(new List<TReturn>(),
                   (collector, item) =>
                   {
                     try
                     {
                       act(item);
                     }
                     catch (TException ex)
                     {
                       collector.Add(exceptionHandler(ex));
                     }
                     return collector;
                   });
}

// e questo mi permette di scrivere:

[TestMethod]
public void TestMethod1()
{
   var expected = new[] { "error", "error", "error" };

   var actual = new[] { 1, 2, 3, 4, 5, 6 }
       .CollectErrors(i => { if (i > 3) 
                               throw new IndexOutOfRangeException(); 
                             },
                      (IndexOutOfRangeException e) => "error");

   Assert.IsTrue(actual.SequenceEqual(expected));
}

certo non è una sintassi molto chiara come potrebbe essere in Scala, ma non è poi tanto male, no?

mercoledì 13 aprile 2011

Scala e MongoDB

Dal sito ufficiale di MongoDB si apprende che Casbah è il driver ufficiale per Scala per MongoDB. Queste API sono molto di basso livello, in quanto wrappano il driver JAVA per MongoDB. Ad esempio, per creare un utente nella nostra collection per MongoDB dobbiamo scrivere una cosa del genere:

val onof80 = MongoDBObject("userName" -> "onof80",
                           "openId" -> "http://.....",
                           "age" -> 31,
                           "interests" -> "Java,Scala")

che si mappa in un oggetto JSON simile a questo:
{ 
  userName : "onof80",
  openId : "http://.....",
  age : 31,
  interests: "Java,Scala"
}
Interessante, ma sono abituato a mongo-scala-driver, che mi permette di definire una classe per l'oggetto User:
class User(
   userName : String, 
   openId : String,
   age : Option[Int], 
   interests : String
) extends MongoObject {
     override def toString: String = userName
}
e quindi, al prezzo di un companion object che ometto, di riutilizzarlo:
val onof80 = new User("onof80", "http://.....", 31, "Java,Scala");

(dbCollection of User) += onof80;

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.

lunedì 28 marzo 2011

Teorema CAP: disponibilità

Riguardo al mio articolo su MokaByte, ho avuto uno scambio di e-mail con alcuni lettori riguardo il teorema CAP, in particolare sulla disponibilità del sistema. Il primo è stato Enrico Oliosi che mi ha chiesto:
Se desidero mantenere la consistenza di dati su tutti i nodi e voglio che il sistema sia tollerante ai guasti, in che modo posso perdere la disponibilità del sistema? Questa perdita si riferisce all'interno sistema o ad un singolo nodo?
Premettendo che non sono un ricercatore né un progettista di database NoSql (anzi, se a qualcuno di loro capitasse di leggere, lo invito a commentare eventuali inesattezze o madornali errori) provo a rispondere in base a quello che ho letto sull'argomento. La domanda, mi pare possa essere riassunta in:
Cosa significa che il sistema non è disponibile? Cos'è un sistema non disponibile ma tollerante?
Dec pdp-6.lg Tempo fa ho letto un post molto interessante di Daniel Abadi, che secondo me può essere molto d'aiuto per rispondere. Se, calando il teorema CAP in pratica, per disponibilità si intende la possibilità di trovare una replica dei dati in caso di fallimento, cosa succede dunque ai sistemi AP, CP e CA in caso di network partition? Abadi dice: in realtà sembrerebbe che non ho tre alternative, ma solo due: AP oppure CP/CA, ossia consistenza oppure no, perché se voglio la consistenza:
  • o non sono tollerante al partizionamento (il sistema non scambia messaggi, quindi in pratica non funziona)
  • oppure non è disponibile (quindi in pratica non funziona).
Abadi vede in questo un'incompletezza del teorema CAP, dice che, per essere utile, un teorema a riguardo dovrebbe considerare anche la latenza del sistema e si dovrebbe suddividere l'analisi in due parti: in caso di funzionamento normale e in caso di partizionamento della rete, perché in effetti queste proprietà si esplicano in un certo istante nel tempo.
Secondo me, il punto è che, al di là di dispute accademiche, i vari sistemi Sql o NoSql, non rinunciano del tutto a una delle tre proprietà, ma di fatto ne rilassano una o più di una per raggiungere determinati scopi e in determinati tempi. Tant'è che Abadi porta ad esempio PNUTS che sembra garantire una sola proprietà. In realtà, rilassa sia C e A in favore di una più bassa latenza. Ecco perché spesso è difficile catalogare un sistema secondo la nomenclatura CAP. Quello che si può fare è dare un'indicazione di massima. Tutto ciò perché CAP stabilisce quello che non può succedere in un istante, non quello che succede in generale. Detto ciò, in cosa si traduce la perdita di disponibilità? In un fallimento dell'intero sistema o del singolo nodo? Se la disponibilità è la possibilità del sistema di supplire al fallimento di un nodo, quindi disponibilità rilassata significa che al fallimento di un nodo il sistema non riesce ad andare avanti come se niente fosse, ma il come dipende da come è stato progettato il sistema.
Provo, ad esempio ad ipotizzare come i tre sistemi potrebbero comportarsi in caso di partizionamento, a causa della rottura di un nodo:
  • CA: i dati rimangono consistenti, il nodo rotto è stato sostituito da una replica che ha dati consistenti, ma alcune funzioni sono state disabilitate come la scrittura. Classica situazione RDBMS.
  • AP: perdita di consistenza, le due sottoreti continuano a funzionare (essere disponibili) in autonomia, il nodo rotto è stato sostituito da una replica in una delle sottoreti, ma i dati tra le reti divergono oppure i dati tra le reti non divergono ma la replica non ha dati consistenti con il nodo rotto (si sono persi gli ultimi n update). Quando la rete si metterà a posto, la consistenza (forse) si raggiungerà a regime (tipica situazione NoSql).
  • CP: le due sottoreti continuano a funzionare autonomamente (P) e ad avere dati tra loro consistenti (C) perché c'è un failover che ri-assembla la rete in un altro modo, ma poiché la disponibilità non è garantita, ci potrebbe essere un transitorio in cui il sistema non è per nulla raggiungibile, oppure che ci saranno diversi operazioni fallite (altra situazione NoSql).
Ma il partizionamento potrebbe essere dato anche da altri fattori, o in altre modalità e il comportamento potrebbe non essere quello, perché magari i nodi non hanno tutti la stessa importanza o le stesse funzioni. Dipende da come è stato architettato il sistema.

martedì 8 marzo 2011

Vi presento GridGain

Come promesso, vi presento GridGain. Si tratta di un framework per permettervi di fare programmazione distribuita in modo molto semplice. Ciò che lo rende veramente potente è il supporto per Scala, infatti Scala dà il meglio di sé proprio in questi contesti. Supponete di avere una rete con 5 nodi e la vostra applicazione suddivide un certo carico di un lavoro su tutti i nodi. Se il sistema scala orizzontalmente e passiamo a 50 nodi, dobbiamo rivedere tutto il programma? Non se usate GridGain. Se inoltre siete stati bravi, e avete applicato i bei paradigmi funzionali alla vostra applicazione, il vostro codice è già scalabile.
Ad esempio, avete una funzione in Scala per calcolare la costante di Nepero:
object Program {

  def invFact(i : Int) : BigDecimal = i match {
    case 0 => BigDecimal(1)
    case n if n > 0 => (BigDecimal(1) / BigDecimal(n)) * invFact(n - 1)
  }

  def neper(iterations : Int) = {
    (BigDecimal(0) /: ((1 to iterations) map invFact))( _ + _)
  }

  ...
}
Dove avete utilizzato la formula:
All'aumentare di iterations aumenta la precisione. È ovvio che può essere molto migliorato, è solo un esempio. Supponiamo adesso che volete farlo andare in parallelo. Con GridGain è semplicissimo:
  1. accedete i vostri nodi (quanti ne volete/potete) lanciando ggstart. Ogni nodo si mette in attesa:
  2. importate le librerie di gridgain nel progetto e modificate il codice in modo da utilizzare gridgain per il calcolo:
      def neper(iterations : Int) = {
        grid !*~ (
              for (w <- 1 to iterations) yield () => {
                val ret = invFact(w)
                println("Ricevuto: " + w + " - Restituito: " + ret)
                ret
              },
              (s: Seq[BigDecimal]) => (BigDecimal(0) /: s)(_ + _)
            )
      }
      
      def main(args : Array[String]) : Unit = {
        scalar {
            val n = neper(100);
            println("e = " + n)
        }
      }
    
Se, per esempio, accendiamo un solo nodo, a parte quello da cui fate partire l'applicazione, quindi 2 nodi in tutto, vedrete che il carico si è suddiviso grosso modo a metà (il balancing viene fatto dinamicamente). L'output del nodo pricinpale è:
Ricevuto: 37 - Restituito: 7.265460179153071315382745030722890E-44
Ricevuto: 71 - Restituito: 1.175808554667930839412331276025168E-102
Ricevuto: 15 - Restituito: 7.647163731819816475901131985788074E-13
Ricevuto: 57 - Restituito: 2.467495709560789306494418417905353E-77
Ricevuto: 27 - Restituito: 9.183689863795546148425716836473919E-29
Ricevuto: 47 - Restituito: 3.866628513960593886829197697871059E-60
Ricevuto: 51 - Restituito: 6.446959640457172680454177834252131E-67
Ricevuto: 21 - Restituito: 1.957294106339126123084757437350544E-20
Ricevuto: 17 - Restituito: 2.811457254345520763198945583010321E-15
Ricevuto: 61 - Restituito: 1.970131956802168311835039121583386E-84
Ricevuto: 59 - Restituito: 7.210682961895936021316243184995189E-81
Ricevuto: 63 - Restituito: 5.043860616493006430709265544248299E-88
Ricevuto: 9 - Restituito: 0.000002755731922398589065255731922398590
Ricevuto: 31 - Restituito: 1.216125041553517949629974685692294E-34
Ricevuto: 19 - Restituito: 8.220635246624329716955981236872284E-18
Ricevuto: 53 - Restituito: 2.339245152560657721500064526216304E-70
Ricevuto: 35 - Restituito: 9.677592958631890992089816380922888E-41
Ricevuto: 77 - Restituito: 6.887854405285506994393217621555398E-114
Ricevuto: 67 - Restituito: 2.741896188035459954765761198513713E-95
Ricevuto: 85 - Restituito: 3.549744558233655586243013385073271E-129
Ricevuto: 81 - Restituito: 1.724992688482351758285854365654940E-121
Ricevuto: 99 - Restituito: 1.071510288125466923183546759519195E-156
Ricevuto: 97 - Restituito: 1.039579281539328008872677066085523E-152
Ricevuto: 83 - Restituito: 2.534517614578830088577511556942316E-125
Ricevuto: 13 - Restituito: 1.605904383682161459939237717015495E-10
Ricevuto: 25 - Restituito: 6.446950284384473396194853219204692E-26
Ricevuto: 55 - Restituito: 7.876246304918039466330183589953885E-74
Ricevuto: 7 - Restituito: 0.0001984126984126984126984126984126985
Ricevuto: 3 - Restituito: 0.1666666666666666666666666666666666
Ricevuto: 87 - Restituito: 4.744379254522394528525813131613567E-133
Ricevuto: 29 - Restituito: 1.130996288644771693155876457693833E-31
Ricevuto: 11 - Restituito: 2.505210838544171877505210838544173E-8
Ricevuto: 93 - Restituito: 8.644742106836940619829775373573826E-145
Ricevuto: 89 - Restituito: 6.057685462873333156953285408086779E-137
Ricevuto: 73 - Restituito: 2.237078680875058674680995578434491E-106
Ricevuto: 23 - Restituito: 3.868170170630684037716911931522814E-23
Ricevuto: 49 - Restituito: 1.643974708316579033515815347734293E-63
Ricevuto: 91 - Restituito: 7.396441346609686394326355809629767E-141
Ricevuto: 33 - Restituito: 1.151633562077195028058688149329824E-37
Ricevuto: 69 - Restituito: 5.843768516699616271879286441845085E-99
Ricevuto: 45 - Restituito: 8.359650847182803983324725422797232E-57
Ricevuto: 5 - Restituito: 0.008333333333333333333333333333333330
Ricevuto: 65 - Restituito: 1.212466494349280391997419601982764E-91
Ricevuto: 43 - Restituito: 1.655210867742195188698295633713852E-53
Ricevuto: 95 - Restituito: 9.680562269694222418622368839388382E-149
Ricevuto: 75 - Restituito: 4.030772397973078693118910952134216E-110
Ricevuto: 1 - Restituito: 1
Ricevuto: 39 - Restituito: 4.902469756513543397694159939759036E-47
Ricevuto: 41 - Restituito: 2.989310827142404510789121914487217E-50
Ricevuto: 79 - Restituito: 1.117795262136563939369233628944401E-117
Sum = 1.71828182845904523536028747135266241129844606162999606421881338461047508297046530851272129739288024500806666138654505778398426211369899599762685530664760916526029476793035635790672605174468695
[18:09:56] GridGain stopped OK [uptime=00:00:01:898]
cioè ha eseguito 55 iterazioni. Le altre sono state eseguite dall'altro nodo (vi risparmio l'output, che vedreste nella sua console). Se a runtime avessi lanciato ancora un terzo nodo, si sarebbe preso il proprio carico di lavoro e tutto il sistema avrebbe scalato di conseguenza, automaticamente!

Notare che sono stato comunque costretto a modificare il codice originario. Perché? Perché non ho fatto il bravo, cioé non ho utilizzato né un monad writer né un monad state. Li vedremo in un prossimo post. :)

onof

lunedì 7 marzo 2011

Codemotion 2011

I talk a cui ho assistito:

  1. Database NO-SQL: Anything else? di Giorgio Desideri.L'ho trovato interessante, soprattutto perché conosco poco Cassandra. Voto: 7 su 10.
  2. HTML5 Up and Running, di Marco Casario. Molto bello, sono stati evidenziati i punti di forza e le difficoltà sul cammino di HTML5. Voto: 8 su 10.
  3. Web, Apps, Mobile, Flash Html 5...Adobe c'è! di Andrea Amadeo. Le strategie di Adobe per il futuro. Voto: 7 su 10.
  4. L'azienda Oracle. Niente di nuovo, più qualche rassicurazione sul futuro di Java. Voto: 6 su 10.
  5. Distributed Programming with GridGain and Scala, di Nikita Ivanov. La potenza espressiva di Scala in un framework molto promettente. Voto: 9 su 10.
  6. Hacking Infinispan: the new open source data grid meets NoSQL, di Manik Surtani. I punti di forza di Infinispan, presentati in modo molto puntuale. Da notare: in arrivo supporto per JPA! Voto: 8 su 10.

Il talk che mi è piaciuto di più sicuramente quello su GridGain (nei prossimi giorni seguirà un post dedicato :) ). A seguire, il talk di Marco Casario, molto bello: il suo libro su HTML5 non mancherà nella mia libreria.

Purtroppo non mi è stato possibile, ma avrei voluto vedere anche:

Da parte mia ringrazio gli organizzatori e gli speakers, un ottimo evento.

venerdì 4 marzo 2011

Primo sguardo a Guice

Da una recente discussione sul blog di Debasish Ghosh, mi è nata la curiosità di guardare questo container di cui avevo sentito parlare. Una caratteristica che mi ha colpito è il modo di definire come istanziare gli oggetti annotando un metodo, ad esempio in questo modo:
public class OrderProcessingModule extends AbstractModule {
   
  ...

  @Provides
  IOrderRepository createRepo() {
     RemoteOrderRepositoryAdapter repo = new 
       RemoteOrderRepositoryAdapter(ThirdPartyObject.getFoo());
     repo.setStuff(ThirdPartyObject.instance.createStuff());
     // ... altro codice per inizializzare repo
     //
     return repo;
  }
}
che, da quanto ho capito (ma ho fatto solo un analisi sommaria) è anche l'unico metodo per iniettare componenti di terze parti.