mercredi 22 juillet 2009

Java était (et est toujours, d'ailleurs) un langage "purement orienté objet". Quelle décision étrange. Non seulement Java est, selon moi, moins orienté objet que C++ (presque pas de contrôle sur la fin de vie d'un objet, notamment), mais ça voulait également dire qu'il ne pouvait faire rien d'autre que de la POO. Pourtant, je me dis que du procédural, parfois, c'est utile.

C# répète à peu près les mêmes erreurs que Java pour la POO, mais compense en s'ouvrant à d'autres paradigmes. Dans sa version 3.0, C# a donc mis un pied (ou plutôt le gros orteil) dans le monde de la programmation fonctionnelle avec des lambda expressions et des expression trees (bon, on pouvait déjà faire ça depuis 1998 en C++ avec des templates, mais bel effort quand même). Et depuis sa toute première version, C# supporte un hybride entre méthodes et fonctions libres sous la forme de délégués (un pointeur déguisé sur une fonction, en fait). Bon, d'accord, je pourrais également faire le parallèle avec C++, mais pourquoi taper sur quelqu'un qui est à terre?

Je sais pas pour vous, mais moi, écrire une classe pour ne l'utiliser qu'une seule fois, comme foncteur sans état en plus, je trouve ça lourd. C'est d'ailleurs pourquoi, en C++, je préfère les fonctions aux foncteurs lorsque c'est possible: c'est beaucoup moins verbeux (mais bon, une fonction n'a pas d'état, alors il faut parfois sortir les foncteurs). Alors bon, quand je vois, dans le .NET Framework, une méthode qui attend un IEqualityComparer<T>, je sacre. Pourquoi devrais-je implémenter une classe pour une fonctionnalité qui n'existera que là? Ne serait-il pas plus simple de passer un délégué à la place? Martin Fowler n'a-t-il pas dit que la généricité préventive était néfaste?

J'ai donc retroussé mes manches, et voici ce à quoi je suis arrivé:


public static class EqualityComparer
{
private class DelegateComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equalityFunction; // prend deux T en paramètre et retourne un bool
private readonly Func<T, int> _hashCodeFunction; // prend un T en paramètre et retourne un int

public DelegateComparer(Func<T, T, bool> equalityFunction, Func<T, int> hashCodeFunction)
{
if (equalityFunction == null || hashCodeFunction == null)
throw new ArgumentNullException();
_equalityFunction = equalityFunction;
_hashCodeFunction = hashCodeFunction;
}

public bool Equals(T x, T y)
{
return _equalityFunction(x, y);
}

public int GetHashCode(T obj)
{
return _hashCodeFunction(obj);
}
}

public static IEqualityComparer<T> Make<T>(Func<T, T, bool> EqualityFunction, Func<T, int> HashCodeFunction)
{
return new DelegateComparer<T>(EqualityFunction, HashCodeFunction);
}
}

Bon, quel intérêt? Eh bien, admettons (cet exemple utilise des lambdas, mais vous pourriez très bien passer des méthodes, tant qu'elles ont la bonne signature):

var absComparer = EqualityComparer.Make<int>(
(p1, p2) => Math.Abs(p1) == Math.Abs(p2),
p => Math.Abs(p).GetHashCode());

Et voilà, un IEqualityComparer<T> en une ligne (étalée sur plusieurs lignes pour plus de clarté)! C'est tu pas wonderful?

Dites-moi ce que vous en pensez. Comme toujours, ce n'est pas final.

1 commentaires:

Vincent a dit…

Arrête de cracher sur ton gagne-pain !! (bien que plus pour très longtemps :)

Enregistrer un commentaire