Java et la programmation fonctionnelle

Maintenant que je commence à prendre un peu plus en main la programmation fonctionnelle, comme vu dans mes précédents articles sur Scala ou mon projet Scalable Explorer, je dois reconnaitre qu'elle apporte une nouvelle façon de penser et surtout des facilités d'écriture (grâce à "l'intelligence" des compileurs) non négligeables. Le passage de Java à Scala est donc relativement aisé et permet une ouverture d'esprit pour la compréhension et la prise en main des nouveaux concepts.

Armés de ces nouveaux outils, faisons un petit retour en arrière... Qu'en est-il de la programmation fonctionnelle en Java? Il est impossible que ces concepts ne puissent pas lui être appliqués, même si on connait la rigueur de ce langage, qui en est devenue une caractéristique majeure. Je vais donc ici exposer comment certains principes fonctionnels peuvent être reproduits dans le monde Java.

Les frameworks

Il existe différents frameworks apportant les outils ou la syntaxe nécessaires à la mise en oeuvre des concepts fonctionnels : fun4j ou Functional Java sont les plus répandus, mais ils ont du mal à s'imposer : peu documentés et parfois mal maintenus, ils ne paraissent pas être la meilleure alternative.

Les "Functors"

La fondation Apache a mis en place les "Apache Commons Collection Utilities", une collection de librairies et d'objets, dans le but d'apporter une "functional touch" au code Java. Ces "functors" ou "functional objects" sont en fait un lot d'interfaces pour créer de nouveaux comportements, essentiellement pour le traitement des collections. Trois concepts majeurs donc : Transformer, Closure et Predicate.

Les Closures

Les closures sont des fonctions qui vont être exécutées sur tous les objets d'une liste. On peut ainsi créer rapidement des traitements de groupe (utiliser ou modifier les objets de la liste, sans modifier le contenu de la liste elle-même). Par exemple :

public static void main(String[] args) {
   List<String> list = Arrays.asList("Apache!","Java!","Functors!");
   CollectionUtils.forAllDo(list, new Closure() {  
     public void execute(Object o) {
       System.out.println(o.toString().replace("!","..."));  
     }  
   });
}
/* Sortie :
Apache...
Java...
Functors...
*/

Les Transformers

Le but ici est de créer une liste d'objets à partir d'une autre, totalement différente (pratique dans le cas de conversions de bean par exemple) :

public static void main(String[] args) {  
  Collection<String> strings = Arrays.asList("Optimus Prime", "Bumblebee", "Megatron", "The fallen");  
  Collection<Autobots> bots = CollectionUtils.collect(strings, new Transformer() {  
      public Object transform(Object o) {  
          return new AutoBot(o.toString());  
      }  
  });  
  CollectionUtils.forAllDo(bots, PrintIt.getInstance() );  
}
/* Sortie :
Optimus Prime à vos ordres!
Bumblebee à vos ordres!
Megatron s'est réveillé...
The fallen prendra sa revanche!!
*/

Les Predicate

Ils sont utilisés principalement pour filtrer des listes : leur rôle est de simplement tester un objet et renvoyer true ou false selon que l'objet passe ou non le filtre :

public static void main(String[] args){
  List<Integer> peopleAges = Arrays.asList(5,18,9,24,35,44,11);  
  Collection adults = CollectionUtils.predicatedCollection(peopleAges,  
      new Predicate() {  
          public boolean evaluate(Object o) {
              Integer num = (Integer) o;  
              return num >= 18;
          }  
      }); 
  System.out.println("Adultes de la liste : ");
  CollectionUtils.forAllDo(numbersOnlyList, PrintIt.getInstance() ); 
}
/* Sortie :
Adultes de la liste : 
18,24,35,44
*/

La librairie Apache fournit également un certain nombre de prédicats prédéfinis pour ce genre de test simple (nullité, comparaison...). Il est aussi possible de créer des prédicats plus complexes grâce à des combinaisons, opérées par les interfaces du type Predicates.or ou Predicates.and.

Pour terminer, je vous conseille fortement de lire le livre Common Java Cookbook traitant de ces librairies communes qui facilitent la manipulation des tableaux, collections, strings, dates et autres objets récurrents en Java.

On voit donc que la programmation fonctionnelle pure est loin de vraiment voir le jour en Java : il faut reconnaitre que sa grammaire même (et donc son compilateur) ne s'y prête pas vraiment, puisqu'au fond ce qui caractérise la programmation fonctionnelle est, au delà des concepts qui peuvent être reproduits en Java comme nous venons de le voir, c'est sa syntaxe : le compilateur doit autoriser certaines souplesses et doit être capable de deviner (inférence) tout ce qui ne relève pas de l'algorithme (typage des variables, cast...)

Sources


Fichier(s) joint(s) :



ScalableExplorer : libre et ouvert... à la critique!

Voilà quelques jours que j'ai commencé à m'exercer un peu plus avec Scala et je dois dire que j'en suis très satisfait! Afin de tester un maximum de fonctionnalités du langage, j'ai décidé de créer un projet d'explorateur de fichiers, qui est donc en cours de développement et auquel j'ajouterai des fonctionnalités régulièrement.

Pour l'occasion, j'ai mis en place un espace SourceForge afin de permettre à tout le monde d'accéder aux sources : l'intérêt est de permettre à qui le veut de jeter un oeil au code que j'ai écrit afin de découvrir les fonctionnalités de Scala à l'oeuvre, de me corriger ou de donner son avis, voire de contribuer à l'avancée du projet! ;)

L'application se nomme Scalable Explorer et est dors et déjà disponible. N'hésitez pas à aller consulter les fichiers, j'ai volontairement essayé d'exploiter au mieux la syntaxe et les outils de Scala. Brièvement, les fonctionnalités disponibles :

  • Arborescence des fichiers avec "lazy-loading"
  • Fenêtre d'aperçu pour les fichiers graphiques et textuels
  • Barre de menu avec raccourcis clavier
  • Menu contextuel (clic-droit) sur l'arbre (JTree)

Comme décrit sur SourceForge, ce projet est bien entendu en phase naissante, "pre-alpha" et est donc voué à largement évoluer dans les semaines à venir. Une fois de plus, si vous êtes intéressé(es), je vous invite à participer, que ce soit en commentant ce qui est mis en place ou en ajoutant des fonctionnalités! Pour information, le projet est développé sous Netbeans et peut être récupéré via SVN ou CVS.

[Edit]Nouvelles fonctionnalités au 18/11/10 :

  • Autoscroll sur le Jtree
  • Drag and drop pour le déplacement de fichiers

Fichier(s) joint(s) :



Java et Business Intelligence avec JasperSofts

Pour faire suite à mon article sur la manipulation de documents Office avec Java et rester dans le domaine de l'informatique décisionnelle, voici une présentation de l'outil JasperReports et de son éditeur graphique iReport de la société JasperSofts. Cet exemple se base sur la dernière version (3.7.6 à l'heure de l'écriture) qui offre de nombreuses facilités quant à la mise en place de rapports.

Je vais fournir dans cette article tout le code et les fichiers nécessaires à reproduire l'exemple. En pièce jointe se trouve un lien vers le workspace que j'ai utilisé (diminué des librairies nécessaires à JasperReports pour des raisons de volume d'upload). Je vais donc décrire ici les quelques subtilités que j'ai rencontrées mais le rapport mis en place se compose de la majorité des éléments couramment recherchés (sous-rapport, diagrammes, listes, tableaux croisés, styles, temps d'évaluation...), donc il vous suffira de l'explorer de fond en comble pour trouver votre bonheur! :)

J'ai choisi de créer un rapport au format PDF car à mon sens il est celui qui propose le meilleur rendu pour les différents composants disponibles. Les objets utilisés dans le code sont volontairement simplistes pour ne pas compliquer la compréhension de leur organisation et de leur utilisation dans le modèle.

Voici un aperçu du rapport final créé pour cet article :

Regardons le code de la classe principale qui permet de le générer (Reporter.java) :

package com.developpef;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.FileResolver;

public class Reporter {

private static final String ENCOURAGEMENT = "Bonne lecture!";

private static final String CLASSIQUE = "Hope this helps...";

public static void main(String[] args) {
 List<TestBean> beans = new ArrayList<TestBean>();
 beans.add(getBean(ENCOURAGEMENT));
 beans.add(getBean(CLASSIQUE));

 try {
  FileResolver fileResolver = new FileResolver() {
   @Override
   public File resolveFile(String fileName) {
    URI uri;
    try {
     uri = new URI(Thread.currentThread()
       .getContextClassLoader().getResource(
          "com/resources/" + fileName).getPath());
     return new File(uri.getPath());
    } catch (URISyntaxException e) {
     e.printStackTrace();
     return null;
    }
   }
  };
  HashMap<Object, Object> parameters = new HashMap<Object, Object>();
  parameters.put("REPORT_FILE_RESOLVER", fileResolver);
  URL jasperModelUrl = Thread.currentThread().getContextClassLoader()
    .getResource("com/resources/jasper/reportTest.jasper");
  URL resourceFolder = Thread.currentThread().getContextClassLoader()
    .getResource("com/resources/jasper/");
  InputStream stream = new FileInputStream(new File(jasperModelUrl
    .toURI()));
  JRBeanCollectionDataSource datasource = new JRBeanCollectionDataSource(beans);
  JasperPrint jasperPrint = JasperFillManager.fillReport(stream,
    parameters, datasource);
  File finalPdfPath = new File(resourceFolder.toURI().getPath()
    + "reportTest.pdf");
  JasperExportManager.exportReportToPdfFile(jasperPrint, finalPdfPath
    .getAbsolutePath());
  Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler "
    + finalPdfPath.getAbsolutePath());
 } catch (Exception e) {
  e.printStackTrace();
 }
}

private static TestBean getBean(String conclusion) {
 TestBean bean = new TestBean();
 bean.setAuthor("Developpef");
 bean.setHomeUrl("http://developpef.blogspot.com/");
 bean.getStatsHolder().getStats().add(new StatBean("Mozilla", 60));
 bean.getStatsHolder().getStats().add(new StatBean("IE", 25));
 bean.getStatsHolder().getStats().add(new StatBean("Others", 15));
 bean.getStatsHolder().getStatsOS().add(new StatBean(502, "Windows"));
 bean.getStatsHolder().getStatsOS().add(new StatBean(158, "Linux"));
 bean.getStatsHolder().getStatsOS().add(new StatBean(12, "Other Unix"));
 bean.getArchives().add(new ArchiveBean("Eclipse", "Septembre", 2));
 bean.getArchives().add(new ArchiveBean("Eclipse", "Octobre", 7));
 bean.getArchives().add(new ArchiveBean("Java", "Septembre", 10));
 bean.getArchives().add(new ArchiveBean("Java", "Octobre", 3));
 bean.getArchives().add(new ArchiveBean("Java", "Novembre", 6));
 bean.getJavaBeans().add(new StatBean(null, 0));
 bean.getJavaBeans().add(new TestBean());
 bean.getJavaBeans().add(new ArchiveBean(null, null, 0));
 bean.getJavaBeans().add(new StatsHolder());
 bean.setJavaTxt1("Ce texte est issu de Java et fait donc partie du contenu "
   + "dynamique du rapport. "
   + "Seuls les quelques POJO listés ci-dessous ont suffit à créer "
   + "tous les graphiques et autres listes présentés ici :");
 bean.setJavaTxt2("Ce rapport assez simple ne se compose que d'une seule page mais est"
   + " édité pour 2 éléments, comme le prouve le code barre basé sur le hashCode"
   + " de ce texte. " + conclusion);
 return bean;
}
}

Tout commence par la création de la liste de beans qui sera passée comme source de données au rapport. Un rapport sera créé pour chaque bean, ce qui dans cet exemple résultera en un PDF de 2 pages. Ces beans doivent être différentiables par le template, c'est la raison pour laquelle il est fortement conseillé de surcharger les méthodes equals() et hashCode() sous peine de voir le rapport répété X fois pour les même données. Ici la distinction se fait grâce à la toute dernière phrase du rapport (variables statiques).

L'en-tête contient une image de fond. Pour permettre au moteur du template de pouvoir la résoudre dans notre classpath et l'insérer dans le PDF, il est indispensable de lui injecter un FileResolver lui permettant d'accéder à tous les fichiers nécessaires (images, sous-rapports...). Pour cela, il faut utiliser le ContextClassLoader afin de parcourir les ressources. Le paramètre fileName correspond au chemin des éléments indiqué dans le template ("images/..." ou "jasper/..").

Il faut également savoir que JasperReports se base sur tout un tas de librairies standards pour créer ses composants. Si vous rencontrez quelques difficultés, il vous suffit de vous rapporter aux documentations respectives :


Fichier(s) joint(s) :



Une Pizza aux 3 langages...

Après avoir lu pas mal d’articles sur Scala et les generics de Java, j’ai essayé de trouver un lien entre tout cela. Pour ce faire, rien de plus simple (!) : prenez quelques tranches de Scala ("high-order functions"), ajouter des bons morceaux de généricité (polymorphisme paramétrique), déposez le tout sur une bonne pâte (programmation fonctionnelle) et introduisez dans un four thermostat 100 (compileur Java). Vous obtiendrez : Pizza !

Plus sérieusement, voici concrètement ce qu’est Pizza, son origine et ses implications dans le monde Java.

Le projet Pizza

"Back to the 90’s". 1995 exactement. Martin Odersky et Niklaus Wirth étudiants à l’Ecole Polytechnique Federale de Lausanne (Suisse), à l’origine des versions 1.1 à 1.4 du compileur Java, décident de travailler sur un langage fonctionnel compilable en bytecode Java. C’est la naissance du projet Pizza. Le but était de créer un langage à la fois objet et fonctionnel, sans les restrictions de Java. Le premier résultat fût Funnel, un langage minimaliste basé sur les "functional nets" mais qui s’est finalement révélé peu utilisable. Scala est donc issu de la seconde mouture de leurs recherches, plus pragmatique et centré sur l’interopérabilité avec les plateformes standards. Il n’est donc pas exactement une extension de Java même s’il lui est totalement compatible et comparable en terme de performances. La première distribution publique de Scala est née en 2003, sa seconde version actuelle est apparue en 2006.

Implications dans le monde Java

Pizza peut donc être vu comme une supercouche de Java (toute application Java est de facto compatible Pizza), lui ajoutant trois principales fonctionnalités souvent réclamées par la communauté : le polymorphisme paramétrique, les fonctions de haut-niveau ("function pointers") et les types de données algébriques ("case classes" et "pattern matching"), sujets sur lesquels je reviendrai plus loin dans cet article. Pizza essaie donc de retranscrire ces concepts dans le monde Java, autant de manière abstraite que concrète, par transposition (adaptation des concepts) ou définition (nouvelles JSR). Comme le dit son concepteur, "Pizza fits smoothly to Java, with only a few rough edges.". C’est notament grace à la JSR-014 que les generics ont fait leur apparition dans Java et qu’ils sont toujours étudiés par l’intermédiaire du compileur étendu de Java : GJ (Generic Java Language).

Détail des concepts majeurs

Le polymorphisme paramétrique

Un exemple simple et rapide pour comprendre de quoi il s’agit (en Java). Le code suivant est syntaxiquement bon :

List<String> lstr = new ArrayList<String>() ;

Alors que celui-ci ne compilera pas :

List<Object> lstr = new ArrayList<String>() ;

En effet, comme nous l’avons vu dans mon précédent article sur les generics, les types paramétrés ne sont pas covariants! C’est donc ceci que veut résoudre Pizza par l’apport du polymorphisme paramétriques : autoriser l’utilisation de l’héritage dans les generics.

Fonctions de haut-niveau

Autrement appelées "Higher-order functions" ou "functions pointer" en C par exemple, elles peuvent être définies simplement comme des functions acceptant d’autres fonctions en paramètre et étant capables de retourner une fonction en résultat. Un exemple simple en Scala :

val numbers = Array(1,2,3,4,5)
val fact = numbers.reduceLeft(_*_)
println("The factorial of five is :" + fact)

Ici, Array.reduceLeft est une méthode de haut-niveau, à laquelle on indique de parser tous les éléments du tableau et de les traiter deux par deux avec la méthode que nous lui passons _*_ qui peut se comprendre comme x*y.

Types algébriques

En termes de programmation fonctionnelle, un type algébrique est un type de donnée qui peut être instancié de plusieurs façons, notamment avec d’autres types disposant du même genre de constructeur et étant des sous-types du premier. Un petit exemple pour éclaircir cette définition. Supposons que nous disposons des classes ci-dessous (Scala) :

abstract class Tree[+T]
case object Leaf extends Tree
case class Node(elem :T, left :Tree[T],right :Tree[T]) extends Tree[T]

Nous pourrons créer la structure suivante :

Grâce à cette "simple" instanciation :

val tree = Node("F", Node("B",Node("A",Leaf,Leaf),Node("D",Node("C",Leaf,Leaf),Node("E",Leaf,Leaf))),Node("G",Leaf,Leaf))

Ces classes sont appelées "case classes" puisqu’elle permettent d’être évaluée avec le mot clé "case" afin de déterminer facilement leur type (par décomposition). Un autre exemple. Soit la classe :

case class Person(firstName :String, lastName :String)

Instanciée de deux manières :

val mark = Person("Mark", "Thomas")
val henry = Person("Henry", "Hudson")

Ces deux instances peuvent faire l’objet d’une évaluation simple avec la syntaxe suivante, pour par exemple rechercher un membre de la famille "Thomas" :

person match {
 case Person(firstName, "Thomas") => println("You are one of my family member "+firstName)
 case _ => println("Nice to meet you" + firstName)
}

Cette méthode est appelée "pattern matching". Un exemple un peu plus concret est donné sur le blog de Martin Odersky lui-même :

try {
  ...
} catch {
  case ex: IOException => "handle io error"
  case ex: ClassCastException => "handle class cast errors"
  case ex: _ => "generic recovery"
}

Nous venons donc de voir en quoi Scala est un langage fonctionnel et quels sont les avantages qu’il apporte à Java en matière d’architecture ou de manipulation des données. Je vous invite à lire les sources ci-dessous car elles apportent une nouvelle façon de voir et de penser complètement différente de la programmation impérative, très utile pour prendre du recul sur sa façon de travailler...

Sources


Fichier(s) joint(s) :



Débuter avec la programmation fonctionnelle

Cet article fait suite à celui que j'ai récemment écrit sur Scala. Etant débutant avec ce langage et le concept de programmation fonctionnelle, je me suis rendu compte qu'il me manquait certaines notions pour aborder sereinement la découverte de Scala. J'ai donc décidé de trouver des ressources expliquant clairement ces principes et là il y a de quoi baisser rapidement les bras...

Tous ceux qui ont essayé de se lancer dans la compréhension de la programmation fonctionnelle se sont sûrement heurtés, comme moi, à des articles ou tutoriels assez obscures, voire même abscons, car souvent trop élitistes ou destinés à un public industriel ou mathématicien...

Après plusieurs heures de recherches à travers plusieurs pistes, j'ai enfin trouvé quelques perles rares : des articles clairs, exhaustifs, à vocation pédagogique et destinés à des développeurs! Je vous conseille donc vivement d'aller les consulter, cela éclaircit grandement les idées. Bonne lecture!

Sources


Fichier(s) joint(s) :



Premiers pas avec Scala

[Edit]
Pour ceux qui sont arrivés sur cette page et qui débutent avec la programmation fonctionnelle, je vous conseille d'aller lire rapidement mon article à ce sujet pour trouver les donnes ressources pour apprendre toutes les bases et principes nécessaires par la suite.

Le crédo de tout bon développeur est de rester attentif aux nouveaux langages ou outils faisant leur apparition et pouvant lui permettre d'améliorer son travail. C'est la raison pour laquelle j'ai décidé de me lancer dans l'apprentissage du langage Scala...

Pourquoi celui-ci en particulier? Tout simplement car il est le plus proche de Java, au point qu'il est prévu pour s'exécuter dans une JVM et que son bytecode est compatible avec celui de Java (autrement dit, les deux langages peuvent s'interfacer!).

L'intérêt principal de Scala est qu'il est beaucoup moins verbeux que Java et comble certaines lourdeurs de syntaxe, comme par exemple :

// En Java
for(int i=0; i<10; i++){
  System.out.println(i);
}

// En Scala
for(i<-List.range(0,10)) println(i)

ou encore :

// En Java
public static String returnStr(Object data) {
  String result = "";
  if(data!=null){
    result = data.toString();
  }
  return result;
}

// En Scala
def returnStr(data:Object) = if(data!=null) data.toString else ""

Ceci n'est qu'un banal exemple, mais il est certain que Scala offre beaucoup d'avantages à l'usage, notamment grâce au fait que le compileur déduit de lui-même beaucoup de choses quant aux types des variables utilisées : dans le dernier exemple ci-dessus, on voit qu'aucun type de donnée de retour n'est spécifié pour la méthode returnStr et pourtant un code comme celui-ci ne compilera pas :

// Scala
var test:Int = returnStr(data)

Alors bien sûr les puristes de Java comme moi pourront s'exclamer devant ce genre de "laxisme" en terme de rigueur syntaxique, mais il est indéniable que cela permet de gagner en productivité et en légèreté lors de l'écriture du code. Scala a en effet l'avantage de s'appuyer sur l'expérience de Java afin de proposer une syntaxe plus concise et directive en ajoutant l'aspect fonctionnel à la programmation purement objet.

Quel IDE pour commencer?

Le première chose compliquée pour se lancer avec Scala est de trouver le bon IDE. En faisant quelques recherches, on se rend vite compte que le débat est assez important entre Eclipse, Netbeans et IDEA. Pour avoir testé les 3, je recommande vivement Netbeans : le principal défaut du plugin Scala pour Eclipse (Galiléo) est son instabilité... Il n'est pas rare qu'il fige littéralement l'IDE après une recherche d'occurrences ou d'expression. Qui plus est, l'auto-complétion n'est vraiment pas au point. Concernant IDEA, je n'est même pas réussi à installer le plugin! (Problèmes de dépendances à la chaîne...)

Finalement, et même s'il n'est pas encore totalement au point, le plugin Netbeans est largement au dessus des autres : auto-completion satisfaisante, stabilité parfaite. Son seul défaut est la lenteur de compilation : entre 2 et 5 secondes pour 2 classes (environ 100 lignes au total...). Pour l'installer, je vous conseille de suivre cette démarche.

Couplage avec Java

Comme je l'ai dit plus haut, Scala est totalement interfaçable avec Java. Il est donc possible d'utiliser des classes Java dans du code Scala (et c'est même parfois nécessaire).

L'exemple le plus parlant est le support de Swing dans Scala. La plupart des objets courants du package javax.swing.* sont disponibles dans le package scala.swing.*. Ainsi, il est possible d'utiliser par exemple scala.swing.Table et d'obtenir son homologue Java (JTable) par l'intermédiaire de la simple propriété scala.swing.Table.peer.

Je ne fais ici qu'une rapide présentation du langage en lui-même, mais je ne manquerai pas de publier des analyses plus approfondies lorsque j'aurai moi même fais le tour de tout ce que Scala peut apporter à un développeur Java. J'ai listé ci-dessous la documentation la plus pertinente que j'ai parcouru pour le moment. Je vous conseille de lire rapidement les détails sur le site officiel afin de prendre connaissance de l'ampleur des nouveautés disponibles!

Sources


Fichier(s) joint(s) :