Je voudrais vous parler d’une session à laquelle j’ai assisté lors de la conférence NCrafts 2016 qui s’est déroulée à Paris en mai de cette année.
Pour ceux qui ne connaissent pas, NCrafts est un événement où des experts viennent parler de sujets autour du développement logiciel. C’est un moment où l’on peut apprendre, échanger, entendre des retours d’expérience et faire du networking entre deux sessions.
Pleine de surprises, cette année j’ai été séduite par le langage fonctionnel qui était à l’honneur. Il y a eu beaucoup de speakers talentueux et des sessions aussi divertissantes qu’enrichissantes.
J’en profite pour féliciter les organisateurs et notamment à Rui Carvalho et Radwane Hassen qui ont vu leur travail récompensé par de superbes conférences.
J’ai donc décidé de vous parler d’une session qui m’a particulièrement plu : Functional Programming in C# par Thomas Jaskula dont voici le lien : http://bit.ly/1GK7Kil
Durant cette session, Thomas nous fait comprendre la relation naturelle qu’il y a entre injecteurs de dépendance (ID), inversion de contrôle (IoC) et le langage fonctionnel (F# dans son cas). En rajoutant la composante C# dans sa conférence, il veut mettre le focus sur le fait qu’en C# on peut se passer de ID et d’IoC sans trop de difficulté pour gagner en clarté et maîtrise de son code. De la même manière que le F# arrive à s’en passer.
Thomas fait un rappel de ce qu’est le langage fonctionnel, pour nous humbles codeurs qui ne sommes restés qu’à l’étape du C#.
Mais alors, qu’est-ce que le F# ?
Le F# est un langage fonctionnel concis basé sur des fonctions construites comme des blocks. La manière dont il est créé évite des soucis d’effet de bord que l’on pourrait rencontrer dans d’autres langages qui maintiennent un état des objets du programme. En effet, les fonctions en F# retournent seulement des valeurs basées sur les données en entrée. L’état des objets n’est donc n’est pas sauvegardé.
On retrouve relativement souvent de la récursion dans ses fonctions. Et de manière générale, on entend parler de closures lorsque que l’on évoque le langage fonctionnel, d’immutabilité, d’expressions lambda, de fonctions anonymes et de bien d’autres choses encore. Je vous conseille un petit coup d’œil au site officiel de F# – http://fsharp.org/. En effet, il conseille de très bons livres qui expliquent tous ces termes en détail et la section Learn vous permet d’approcher simplement ces thèmes par la pratique.
Alors revenons à nos moutons, pourquoi vouloir se passer d’ID et d’IoC ?
Parce qu’appréhender ces termes, apprendre ce que c’est, à les utiliser et à les maîtriser, c’est long pour quelqu’un d’inexpérimenté. Et au final, on peut se demander ce que ça nous rapporte de plus, à part un investissement d’apprentissage.
Parce qu’en fait dans la majorité des cas, on n’en a pas besoin. Ces mécanismes complexifient le code. Parce que dans la plupart des cas, ils nous rendent la vie plus simple là où il ne faudrait pas. Parce qu’ils peuvent éclipser les vrais problèmes.
J’ai adoré la phrase de Thomas: « fell the pain and think » que l’on pourrait traduire par : « ressentez la douleur et réfléchissez ». Effectivement, ça fait réfléchir…
Pour nous montrer que l’on peut bien s’en passer, même en C#, Thomas va nous expliquer comment mettre en place un mécanisme de pipeline qui en fait, émule le fonctionnement des langages fonctionnels. Tout simplement ! Il fallait y penser !
Comment s’y prend-t-il ?
D’une part, il revient vers la ligne métier. Pour cela, il se concentre sur les commandes (au sens DDD), ce qui fait que le programme va être orienté fonctionnel sans trop se soucier de la technique. Par un système de pipeline que l’on va décrire plus en détail ci-dessous, il injecte ses objets et ses méthodes.
D’appel de fonction en appel de fonction, il compose ainsi son programme jusqu’à obtenir le résultat souhaité. Thomas a pensé à tout : même aux exceptions, ces nasty little things qui pointent le bout de leur nez quand on les attend le moins.
Pour les inclure, il simplifie le problème en disant qu’une fonction peut avoir des paramètres en entrée et doit permettre également de prévoir deux types de résultats en sortie : le résultat attendu ou une exception.
La solution est donc de créer une fonction spéciale (un pipeline) qui va s’occuper d’exécuter d’autres fonctions à la chaine. Le pipeline va avoir pour but de prendre la première fonction en entrée, de l’exécuter et d’en récupérer le résultat. Soit le résultat est une erreur et là, le pipeline s’occupe d’interrompre le traitement soit le retour de fonction est un vrai résultat et il va se charger d’appeler la prochaine fonction chaînée en lui passant le résultat de la première.
Passons à un peu de code maintenant. Thomas a partagé ses sources sur GitHub, avis à ceux qui veulent faire tourner le programme : https://github.com/tjaskula/Talks
La première classe ParseResult nous donne une indication sur le résultat : est-il en succès ou non ? S’en suit deux classes dérivées qui sont assez évidentes à comprendre : Erreur et Succes. Et enfin, on a une méthode d’extension qui permet le chainage entre le résultat de la fonction courante et la fonction suivante si et seulement si la fonction courante a retourné un résultat de type Success.
public abstract class ParseResult<T>
{
public bool IsSuccess { get; protected set; }
}
public class Success<T> : ParseResult<T>
{
public Success(T parsed)
{
Parsed = parsed;
IsSuccess = true;
}
public T Parsed { get; private set; }
}
public class Error<T> : ParseResult<T>
{
public Error(string message)
{
Message = message;
IsSuccess = false;
}
public string Message { get; private set; }
}
public static ParseResult<R> Bind<A, R>(this ParseResult<A> parsed, Func<A, ParseResult<R>> function)
{
var parseda = parsed as Success<A>;
return parseda == null
? new Error<R>(((Error<A>)parsed).Message)
: function(parseda.Parsed);
}
La manière dont on l’utilise :
var funcParsed = new
Parsers.Success(content).Bind(startStriperFunc).Bind(endStriperFunc).Bind(pageParserFunc);
if (funcParsed.IsSuccess)
funcParsedNumber = wordCounter.Count(funcParsed.FromParseResult());
System.Console.WriteLine("Number of words (func composition) : {0}", funcParsedNumber);
System.Console.ReadKey();
On voit bien que l’on peut à l’infini chaîner le Bind.
Et pour finir, la manière dont on l’utilise avec Linq :
int funcParsedNumberLinq = 0;
var funcParsedLinq = from a in content.ToParseResult()
from b in startStriperFunc(a)
from c in endStriperFunc(b)
from d in pageParserFunc(c)
select d;
if (funcParsedLinq.IsSuccess)
funcParsedNumberLinq = wordCounter.Count(funcParsedLinq.FromParseResult());
System.Console.WriteLine("Number of words (func composition Linq) : {0}", funcParsedNumberLinq);
System.Console.ReadKey();
En conclusion
Même si ce code est relativement élégant et qu’il permet de mettre en place une philosophie fonctionnelle, il n’en reste pas moins que je suis d’accord avec Thomas J : finalement si on veut vraiment faire du fonctionnel alors autant utiliser un langage fonctionnel digne de ce nom. Le F# en est un bel exemple. Surtout que la plateforme .net met tout en œuvre pour qu’un .sln puisse accueillir un mélange de projets écrits dans des langages différents. A vous de tester !
Ajouter un commentaire