J'ai été faire SOLIDE en C# pour un joli niveau extrême, ces derniers temps, et à un certain moment de réaliser que je suis essentiellement ne pas faire grand chose d'autre que de la composition de fonctions aujourd'hui. Et après j'ai récemment commencé à regarder les F# encore une fois, j'ai pensé que ce serait probablement la plus appropriée le choix de la langue pour beaucoup de ce que je fais en ce moment, donc je voudrais l'essayer et le port d'un monde réel projet C# F# comme une preuve de concept. Je pense que je pourrais enlever le code (dans un très non-idiomatiques de la mode), mais je ne peux pas imaginer ce qu'une architecture ressemblerait qui me permet de travailler dans la même souplesse qu'en C#.
Ce que je veux dire par là, c'est que j'ai beaucoup de petites classes et les interfaces que je compose à l'aide d'un conteneur IoC, et j'ai aussi utiliser des formes comme Décorateur et Composite beaucoup. Il en résulte une (à mon avis) très flexibles et évolutives architecture d'ensemble qui me permet de facilement remplacer ou étendre les fonctionnalités à tout moment de la demande. Selon la taille de l'changements nécessaires, c'est, pourrais-je seulement besoin d'écrire une nouvelle mise en œuvre d'une interface, remplacer le Cio à l'enregistrement et à faire. Même si le changement est plus grand, je peux remplacer une partie de l'objet graphique tandis que le reste de l'application signifie tout simplement qu'elle a fait avant.
Maintenant, avec F#, je n'ai pas de classes et d'interfaces (je sais que je peux, mais je pense que c'est à côté de la question, quand je veux faire effectivement la programmation fonctionnelle), je n'ai pas de constructeur d'injection, et je n'ai pas du Cio conteneurs. Je sais que je peux faire quelque chose comme un Décorateur motif à l'aide des fonctions d'ordre supérieur, mais ce n'est pas tout à fait semblent me donner le même type de flexibilité et de facilité de maintenance des classes avec le constructeur d'injection.
Tenir compte de ces C# types:
public class Dings
{
public string Lol { get; set; }
public string Rofl { get; set; }
}
public interface IGetStuff
{
IEnumerable<Dings> For(Guid id);
}
public class AsdFilteringGetStuff : IGetStuff
{
private readonly IGetStuff _innerGetStuff;
public AsdFilteringGetStuff(IGetStuff innerGetStuff)
{
this._innerGetStuff = innerGetStuff;
}
public IEnumerable<Dings> For(Guid id)
{
return this._innerGetStuff.For(id).Where(d => d.Lol == "asd");
}
}
public class GeneratingGetStuff : IGetStuff
{
public IEnumerable<Dings> For(Guid id)
{
IEnumerable<Dings> dingse;
// somehow knows how to create correct dingse for the ID
return dingse;
}
}
Je vais dire à mon conteneur IoC pour résoudre AsdFilteringGetStuff
pour IGetStuff
et GeneratingGetStuff
de sa propre dépendance à cette interface. Maintenant si j'ai besoin d'un filtre différent ou enlever le filtre au total, j'ai peut-être besoin la mise en œuvre de l' IGetStuff
, puis il suffit de changer le Cio de l'enregistrement. Tant que l'interface reste la même, je n'ai pas besoin de toucher à des trucs à l'intérieur de l'application. OCP et LSP, activé par le DIP.
Maintenant, je fais quoi en F#?
type Dings (lol, rofl) =
member x.Lol = lol
member x.Rofl = rofl
let GenerateDingse id =
// create list
let AsdFilteredDingse id =
GenerateDingse id |> List.filter (fun x -> x.Lol = "asd")
J'aime la façon dont beaucoup moins de code, c'est, mais j'ai perdent de leur souplesse. Oui, je peux appeler AsdFilteredDingse
ou GenerateDingse
dans le même lieu, parce que les types sont les mêmes - mais comment puis-je décider sur un sans codage en dur elle sur le site d'appel? Aussi, bien que ces deux fonctions sont interchangeables, maintenant, je ne peut pas remplacer le générateur de fonction à l'intérieur d' AsdFilteredDingse
sans modification de cette fonction. Ce n'est pas très agréable.
Prochaine tentative:
let GenerateDingse id =
// create list
let AsdFilteredDingse (generator : System.Guid -> Dings list) id =
generator id |> List.filter (fun x -> x.Lol = "asd")
Maintenant, j'ai la composabilité en faisant AsdFilteredDingse un ordre supérieur de la fonction, mais les deux fonctions ne sont pas interchangeables plus. Sur la deuxième pensée, ils probablement ne devrait pas être de toute façon.
Ce que je pourrais faire d'autre? J'ai pu imiter la "composition de la racine" concept de mon C# SOLIDES dans le dernier fichier du projet F#. La plupart des fichiers sont juste des collections de fonctions, alors j'ai une sorte de "registre", qui remplace le conteneur IoC, et enfin il y a une fonction que j'appelle pour exécuter la demande et qui utilise les fonctions de la "greffe". Dans le "registre", je sais que j'ai besoin d'une fonction de type (Guid> des Coups de liste), que j'appellerai GetDingseForId
. C'est celui que j'appelle, jamais l'individu fonctions définies précédemment.
Pour le décorateur, la définition serait
let GetDingseForId id = AsdFilteredDingse GenerateDingse
Pour supprimer le filtre, je changerais que pour
let GetDingseForId id = GenerateDingse
Le revers de la médaille(?) cela, c'est que toutes les fonctions qui utilisent d'autres fonctions devraient raisonnablement être des fonctions d'ordre supérieur, et mon "registre" aurait de la carte toutes les fonctions que j'utilise, parce que les fonctions réelles définies précédemment peuvent pas appeler toutes les fonctions définies plus tard et, en particulier, ceux de la "greffe". Je pourrais également exécuter dans la circulaire des problèmes de dépendance avec le "registre" des mappages.
Tout cela a un sens? Comment avez-vous vraiment construire une application F# pour être facile à entretenir et évolutives (pour ne pas mentionner testable)?