jeudi 10 septembre 2015

Scala : Fonction Partielle (PartialFunction)

Fonction partielle

Une fonction partielle est une fonction avec un domaine de définition restreint. Elle n'est pas entièrement définie sur l'ensemble des valeurs du type correspondant à l'ensemble de définition. Par exemple, une fonction qui prend un Integer en paramètre et qui n’est pas définie pour toutes les valeurs possibles Integer : la fonction racine carrée n’est pas définie pour les nombre négatifs, la fonction division (x, y) qui à x associe x/y n'est pas définie pour y=0.


En scala une fonction partielle est du type trait PartialFunction[-A, +B] qui étend la fonction unaire ((A) => B) :


trait PartialFunction[-A, +B] extends (A)  B

Une fonction partielle Scala doit obligatoirement définir 2 méthodes : apply et isDefinedAt. Voici quelques exemples de fonction partielle en Scala :

  - List [+A]:


def isDefinedAt(x: Int): Boolean
//Tests whether this sequence contains given index.

def apply(n: Int): A
//Selects an element by its index in the sequence.

   - Map [A, +B] :


def isDefinedAt(key: A): Boolean
//Tests whether this map contains a binding for a key. 

def apply(key: A): B
//Retrieves the value which is associated with the given key.


Exemple pratique


Nous disposons d’une liste de nombres réels. L’objectif ici est de calculer la racine carrée de chaque nombre positif de la liste. Il faudra en effet omettre les nombres négatifs. 

Solution avec Java (<8) :


public class Main {

    public static void main(String[] args) {
        List<Double> result = square(Arrays.asList(-4.0, 1.0, 16.0, 36.0, -81.0, 49.0));
        System.out.println("Reslutat : "+result);
    }

    static List<Double> square(List<Double>  inputs){
        List<Double> result = new ArrayList<Double>();
        for (Double value : inputs){
          if(value >=0){
              result.add(Math.sqrt(value));
          }
        }
        return result;
    }
}

Cette solution procédurale effectue un parcours afin d'omettre les nombre négatifs de la liste. Voyons comment implémenter une solution fonctionnelle avec Scala en utilisant la fonction collect.

Scala dispose d’une fonction collect qui s’applique à une liste. Elle prend en paramètre une fonction partielle (fp) et l’applique à chaque élément de la liste. Pour chaque élément e de la liste, la fonction collect appelle fp isDefinedAt(e). Si cette application retourne truefp apply(e) est appelée sinon l'élément est omis. L’exemple ci-dessus applique la fonction partielle ages du type Map sur une liste de noms names :


Une fonction partielle peut être utilisée conjointement avec la fonction collect afin d’implémenter élégamment le problème de calcul de la racine carrée d’une liste de nombre réels :


  def main(args: Array[String]) = {
    val result = List(-4.0, 1.0, 16.0, 36.0, -81.0, 49.0) collect square
    println("result, " + result)
  }


  val square = new PartialFunction[Double,Double]{
    def apply(v1: Double): Double = Math.sqrt(v1)
    def isDefinedAt(x: Double): Boolean = x match {
      case x => x > 0
      case _ => false
    }
  }

Une solution équivalente simplifiée en utilisant case :


  def main(args: Array[String]) = {
    val result = List(-4.0, 1.0, 16.0, 36.0, -81.0, 49.0) collect square
    println("result, " + result)
  }

  def square : PartialFunction[Double, Double] = {
    case value if(value >= 0) => Math.sqrt(value)
  }


Combinaison de fonctions partielles

Commençons par un petit exercice. Nous disposons d’une liste d’entiers et nous voulons multiplier les nombres impairs par 3 et les nombres pairs par 2. Nous allons donc définir deux fonctions partielles. Une fonction partielle pour les nombres impairs et une autre pour les nombres pairs. Il suffit de combiner les deux fonctions partielles avec la fonction orElse.  Cette fonction s’applique de la manière suivante : 

fp1(e) orElse fp2(e). 

Si la fonction partielle fp1 n’est pas applicable au paramètre e (isDefinedAt(e)=false), fp2 est appliquée au paramètre e. notre implémentation va consister à appliquer la fonction collect avec comme paramètre la combinaison de deux fonctions partielles :


  def main(args: Array[String]) = {
    println("result : " + (List(3, 7, 4, 5, 8, 10, 6, 13).collect(multiplyOddNumber orElse multiplyPeerNumber)) )
  }

  def multiplyOddNumber: PartialFunction[Int, Int] = {
     case x if x % 2 == 1 => 3*x
    }

  def multiplyPeerNumber: PartialFunction[Int, Int] = {
    case x if x % 2 == 0 => 2*x
  }


result : List(9, 21, 8, 15, 16, 20, 12, 39)


La combinaison de nos deux fonctions partielles (multiplyOddNumber orElse multiplyPeerNumber ) nous donne une fonction totale c'est-à-dire une nouvelle fonction définie sur l'ensemble des valeurs du type correspondant à l'ensemble de définition. Nous pouvons donc utiliser la fonction map à la place de collect  :

  def main(args: Array[String]) = {
    println("input, " + (List(3, 7, 4, 5, 8, 10, 6, 13).map(multiplyOddNumber orElse multiplyPeerNumber)) )

  }



Aucun commentaire:

Enregistrer un commentaire