Archivio tag: Guide

Scala e Pattern Matching

Ultimamente ho iniziato a giocare con Scala, passato agli onori della cronaca per essere il linguaggio con cui è scritto Apache Spark, uno dei motori di data-processing più in voga nel mondo dei Big Data (e che ha soppiantato da tempo Pig e altre tecnologie basate su Map&Reduce).

Uno degli aspetti più interessanti, riguarda la possibilità di usare dei Pattern di Matching per tipo (sia scalare che su oggetti) per evitare le righe e righe di codice di instanceof , type ,if-else, o semplicemente realizzare la tanto agognata switch basata su stringhe. Altro utilizzo molto interessante del Pattern Matching potrebbe essere quello di eseguire diverse azioni sulla base del tipo di dato incontrato, durante il traversal di strutture composite, come ad esempio un DOM Xml.

Quello che in Java si scrive con diverse if, in Scala si riduce all’essenziale.
Il seguente pezzo di codice, definisce una funziona X che torna una stringa ed accetta qualsiasi tipo di dato in input (Any in scala è l’equivalente di Object) . Un Esempio classico è quello della decodifica dei parametri passati come argomento al nostro main.

def parseMainArgument(arg:String) = arg match{
  case "--help" | "-h" => displayHelpMessage
  case "--log"  | "-l" => showLog
  case "--verbose" | "-v" => setVerbose
  case _  => showArgumentNotFound
}

Il carattere underscore “_” sta a significare il valore di default, quando nessuno dei suddetti viene invocato, quindi nella switch sopra descritta, sta a rappresentare il caso “else”..

Un esempio di utilizzo di più valori nel match:

def myDecodeParam(funcName: String, value: Any) = (funcName,value) match{
   case ("test",val) => test(val)
   case ("log",msg) => log("messaggio:"+msg)
}

Altro aspetto interessante dei match, è la possibilità di analizzare i tipi di dati, ad esempio:

def myfunc(x:Any):String = x match{
   case i:Int => ""+ i + "è un Intero"
   case d:Double => "" + d + "+ un Double"  
   case s:String => s+ " è una Stringa"
}

Un utilizzo frequente è quello tramite le Case Classes.
Le Case Classes sono classi regolari che esportano in automatico i parametri del costruttore e che è possibile poi scomporre e analizzare attraverso il Pattern Matching. Le Case Classes non richiedono l’utilizzo della new, ed il compilatore genera in automatico il metodo “equals” ed il “toString”.

Un esempio classico, riportato anche dalla documentazione, è quello della definizione di classi case per la scomposizione e risoluzione di formule matematiche, ma che è facile poi utilizzare per creare un DSL applicativo.
In questo esempio definiamo una serie di case classes che descrivono operazioni matematiche che vengono svolte in quello che definiamo un ambiente di test, un insieme di valori che assumono le variabili del calcolo. Invece di usare una collection per la definizione delle variabili, lo facciamo utilizzando le funzioni. La notazione

{case "x" => 5}

ad esempio crea una funzione che quando riceve x in input ritorna il valore “5”.
la sua definizione è :

type NomeFunzione = String => Int

dove String è l’input, e Int l’output.

In questo esempio usiamo anche i Traits,cioè i tratti, le caratteristiche da aggiungere alle classi. Sono interfacce con la possibilità di avere implementazioni parziali.

trait Espressione {
    def decodifica(caso:AmbienteTest.Ambiente) : String = {
      this match {        
        case Somma (a,b) => "(" + a.decodifica(caso) +" + " + b.decodifica(caso) + ")"
        case Sottrai (a,b) => "(" + a.decodifica(caso) +" - " + b.decodifica(caso) + ")"        
        case Moltiplica (a,b) => "(" + a.decodifica(caso) +" * " + b.decodifica(caso) + ")"
        case Dividi (a,b) => "(" + a.decodifica(caso) +" / " + b.decodifica(caso) + ")"        
        case Variabile(x) => "" + caso(x)
        case Costante(n) => "" + n
      }
    }
   
    def risolvi(caso:AmbienteTest.Ambiente) : Int = {
      this match {  
        case Somma (a,b) =>a.risolvi(caso) + b.risolvi(caso)
        case Sottrai (a,b) =>a.risolvi(caso) - b.risolvi(caso)
        case Moltiplica (a,b) =>a.risolvi(caso) * b.risolvi(caso)
        case Dividi (a,b) =>a.risolvi(caso) / b.risolvi(caso)
        case Variabile(x) => caso(x)
        case Costante(n) =>  n
      }
    }
   
}

case class Somma (a:Espressione, b:Espressione) extends Espressione
case class Sottrai (a:Espressione, b:Espressione) extends Espressione
case class Moltiplica (a:Espressione, b:Espressione) extends Espressione
case class Dividi (a:Espressione, b:Espressione) extends Espressione
case class Variabile (x:String) extends Espressione
case class Costante (n:Int) extends Espressione
package object  AmbienteTest {type Ambiente =  String => Int}

object ExpressionDecoder {
  def main(args:Array[String]){
     val myTest:AmbienteTest.Ambiente = {case "x"=>1  case "y"=>2}
     val exp:Espressione =Sottrai(Moltiplica(Somma(Somma(Costante(5),Variabile("x")),Variabile("y")),Costante(2)),Variabile("x"))
     println("Espressione: "+exp.decodifica(myTest));
     println("Risultato: "+exp.risolvi(myTest));
  }
 
}

Il risultato in console dell’esecuzione del codice sopra esposto è:

Espressione: ((((5 + 1) + 2) * 2) - 1)
Risultato: 15

Come possiamo vedere, i pattern matching con Scala, aprono tutta una serie di alternative alla programmazione per “IF” , arrivando a rendere più snello e leggibile il nostro codice.

Ottenere le coordinate GPS dal browser con HTML5

Un modo semplice e veloce per ottenere le coordinate gps ,in una pagina html, mediante html5 .

La funziona javascript getCurrentPosition interroga ,se disponibile ,il nostro dispositivo gps (previa richiesta di autorizzazione)  . Se non riesce ad ottenerla da gps può comunque derivarla dall’indirizzo ip, ma il comportamento e le regole sono completamente a carico dell’implementazione del browser.

Questa è la funzione che va usata:

1
2
3
4
5
6
7
8
9
10
11
12
13
if(navigator.geolocation){

navigator.geolocation.getCurrentPosition(successCallback,

errorCallback,

{timeout:60000});

}else{

alert("Il browser non ha un dispositivo GPS");

}

Come vedete la funzione ha due callback, uno che viene invocato in caso di successo e l’altro di errore. Nei parametri e’ possibile impostare il timeout, cioè il tempo di attesa di synch con il gps prima di andare in errore di timeout. Da notare che questo tempo scatta da prima che appaia la popup di richiesta autorizzazione, quindi influisce molto nell’usabilità dell’interfaccia utente. 60 secondi in genere sono un valore sufficiente.

Ora descriviamo cosa deve fare la nostra pagina con i due callback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//callback in caso di successo

function successCallback(position){

alert("Posizione:[lat="+position.coords.latitude+"][long="+position.coords.longitude+"]");

}

//callback in caso di errore

function errorCallback(error){

switch(error.code){

case error.PERMISSION_DENIED: alert('accesso negato');break;

case error.POSITION_UNAVAILABLE:alert('impossibile ottenere posizione');break;

case error.TIMEOUT:alert('timeout');break;

default: alert('errore generico');break;

}

}

Alcuni browser memorizzano l’autorizzazione con durata giornaliera, altri richiedono l’autorizzazione ogni volta.

Una cosa a cui prestare attenzione è il fatto che la funzione è asincrona, quindi difficile da gestire all’interno di un evento di button pressed o cose simili.

Consigli:

Meglio sfruttarlo nell’onload del document , magari facendo una pagina adHoc che aspetti il timeout della funzione mostrando l’icona di loadinprogress, in modo da far capire all’utente che la lettura dei dati dal gps è in corso.

Qui troverete una documentazione accurata, anche se solo in draft, ma vi consiglio di testare le vostre pagine con tutti i browser in quanto hanno comportamenti molto differenti.

http://dev.w3.org/geo/api/spec-source.html