Cet article a fait l'objet d'une traduction automatique.
Le programmeur au travail
S'amuser avec C#
Apprendre une nouvelle langue peut aider les développeurs à apporter de nouvelles perspectives et nouvelles approches pour écrire du code dans d'autres langues, a la c#. Ma préférence personnelle ici est F #, parce que je suis un F # MVP ces jours-ci. Alors que j'ai brièvement abordé la programmation fonctionnelle dans une colonne antérieure (bit.ly/1lPaLNr), je veux ressembler à une nouvelle langue ici.
Pour ce faire, il est probable que le code devra éventuellement être écrit en c# (je vais faire dans le prochain épisode). Mais il peut toujours être utile de coder en F # pour trois raisons :
- F # peut parfois résoudre des problèmes comme cela plus facilement que c#.
- Penser à un problème dans une autre langue peut souvent aider à clarifier la solution avant de réécrire en c#.
- F # est un langage .NET comme son cousin c#. Donc, vous pourriez éventuellement résoudre dans F #, puis le compiler dans un assembly .NET et appeler simplement pour elle de c#. (Selon la complexité de l'algorithme de calcul /, ce serait effectivement la solution plus sain d'esprit.)
Regarder le problème
Envisager un problème simple pour ce type de solution. Imaginez que vous travaillez sur Speedy, une application pour la gestion des finances personnelles. Dans le cadre de l'application, vous devez « concilier » les transactions que vous avez trouvé en ligne avec les transactions, qu'un utilisateur a entré dans le $ $ etAPP. Le but ici est de travailler par l'intermédiaire de deux listes de pour la plupart des données identiques et correspondent aux éléments identiques. Ce que vous faites avec ces éléments sans pareil n'est pas encore non précisée, mais vous devrez les capturer.
Ans, j'ai fait quelques marchés de la compagnie « intuitive » qui fait ce qui était alors la plus populaire application de PC pour gérer vos opérations bancaires. Il s'agissait d'un problème réel, que je devais travailler là-bas. Il a été spécifiquement pour l'affichage de registre de compte chèque après avoir téléchargé les transactions de l'utilisateur tel qu'il est connu par la Banque. J'ai dû concilier les transactions en ligne avec ceux l'utilisateur avait déjà conclu l'app et ensuite demander à l'utilisateur sur toutes les transactions qui ne correspond pas à.
Chaque transaction se compose d'un montant, une date de transaction et un descriptif « commentaire ». Voilà le hic : Les dates ne correspond pas toujours à et pas plus que les commentaires.
Cela signifie que les données seulement réel crédibles que je pourrais comparer a été le montant de la transaction. Heureusement, c'est assez rare dans un mois donné que deux transactions seront absolument identiques à la sou. C'est donc une solution « assez bonne ». Je vais revenir en arrière et confirmer qu'ils sont en fait un match legit.Juste pour compliquer les choses, les deux listes entrants ne doivent pas correspondre à la longueur.
Un F #ing Solution
Il existe des principes sur les langages fonctionnels qui dominent comment « que sur le plan fonctionnel. » Dans ce cas, un des premiers est qu'ils préfèrent la récursivité sur itération. En d'autres termes, alors que le développeur de formation classique voudront immédiatement de se mettre debout quelques imbriqués pour les boucles, le programmeur fonctionnel voudront récursivité.
Ici, je vais prendre la liste locale des transactions et la distance liste des transactions. Je vais passer par le premier élément de chaque liste. S'ils correspondent, je vais éplucher ces deux au large de leurs listes respectives, écrasez-les ensemble dans la liste des résultats, et appelle de manière récursive à nouveau le reste des listes locales et distantes. Regardez les définitions de type pour ce que je travaille avec :
type Transaction =
{
amount : float32;
date : System.DateTime;
comment : string
}
type Register =
| RegEntry of Transaction * Transaction
En termes simples, je suis définissant deux types. L'un est un type d'enregistrement, qui est en fait un objet sans une certaine notation objet traditionnel. L'autre est un type d'union discriminé, qui est en fait un graphique d'objet/classe déguisé. Je n'obtiendrai pas dans les profondeurs de la syntaxe F # ici. Il ya beaucoup d'autres ressources là-bas pour ça, y compris mon livre, « professionnel F # 2.0"(Wrox, 2010).
Qu'il suffise de dire, ce sont les types d'entrées et sortie, types, respectivement. La raison pour laquelle que j'ai choisi une union discriminée pour que le résultat sera bientôt apparent. Compte tenu de ces deux définitions, il est assez facile de définir le squelette externe de ce que je veux cette fonction pour ressembler à :
let reconcile (local : Transaction list) (remote : Transaction list) : Register list =
[]
N'oubliez pas, dans le langage F #, que les descripteurs de type viennent après le nom. Donc c'est déclarer une fonction qui prend deux transactions répertorie et retourne une liste d'éléments de Registre. Comme l'écrit, il définit pour retourner une liste vide ("[]"). C'est bon, parce que j'ai peux talon maintenant quelques fonctions pour tester — style Test-Driven Development (TDD) — dans une plaine vanille F # console application normale.
J'ai peut et doit écrire dans une infrastructure de test unitaire pour l'instant, mais je peux accomplir essentiellement la même chose en utilisant System.Diagnostics.Debug.Assert et fonctions localement imbriquées à l'intérieur de la main. D'autres peuvent préférer travailler avec le F # REPL, soit en Visual Studio , soit à la ligne de commande, comme indiqué dans Figure 1.
Figure 1 créer un algorithme de Console avec F # REPL
[<EntryPoint>]
let main argv =
let test1 =
let local = [ { amount = 20.00f;
date = System.DateTime.Now;
comment = "ATM Withdrawal" } ]
let remote = [ { amount = 20.00f;
date = System.DateTime.Now;
comment = "ATM Withdrawal" } ]
let register = reconcile local remote
Debug.Assert(register.Length = 1,
"Matches should have come back with one item")
let test2 =
let local = [ { amount = 20.00f;
date = System.DateTime.Now;
comment = "ATM Withdrawal" };
{ amount = 40.00f;
date = System.DateTime.Now;
comment = "ATM Withdrawal" } ]
let remote = [ { amount = 20.00f;
date = System.DateTime.Now;
comment = "ATM Withdrawal" } ]
let register = reconcile local remote
Debug.Assert(register.Length = 1,
"Register should have come back with one item")
0 // Return an integer exit code
Étant donné que j'ai un échafaudage de test de base en place, je vais attaquer la solution récursive, comme vous pouvez le voir dans Figure 2.
Figure 2 utilisation F # Pattern Matching pour une Solution récursive
let reconcile (local : Transaction list)
(remote : Transaction list) : Register list =
let rec reconcileInternal outputSoFar local remote =
match (local, remote) with
| [], _ -> outputSoFar
| _, [] -> outputSoFar
| loc :: locTail, rem :: remTail ->
match (loc.amount, rem.amount) with
| (locAmt, remAmt) when locAmt = remAmt ->
reconcileInternal (RegEntry(loc, rem) :: outputSoFar) locTail remTail
| (locAmt, remAmt) when locAmt < remAmt ->
reconcileInternal outputSoFar locTail remTail
| (locAmt, remAmt) when remAmt > locAmt ->
reconcileInternal outputSoFar locTail remTail
| (_, _) ->
failwith("How is this possible?")
reconcileInternal [] local remote
Comme vous le remarquerez, cela fait un usage assez lourd de F # filtrages. C'est conceptuellement semblable au bloc interrupteur c# (de la même manière, qu'un chaton est conceptuellement semblable à un tigre à dents de sabre –). Tout d'abord, je définis une fonction récursive local (CRE) qui est fondamentalement la même signature que la fonction externe. Il y a un paramètre supplémentaire pour transporter les résultats correspondants jusqu'à présent.
Dans ce cadre, le premier bloc de correspondance examine les deux listes locales et distantes. La première clause de correspondance ([], _) indique si la liste locale est vide, je n'aime pas ce qui est la liste distante (le soulignement est un caractère générique) parce que je suis fait. Il suffit donc de renvoyer les résultats obtenus jusqu'à présent. Il en va de même pour la deuxième clause de correspondance (_, []).
La viande de tout cela vient dans la dernière clause de correspondance. Cela la tête de la liste locale des extraits et lie à la loc de valeur, met le reste de la liste dans locTail, fait de même pour la télécommande en rem et remTail et puis correspond à nouveau. Cette fois, j'ai extrait les champs montant de chacun des deux éléments décollées sur les listes et les lier dans les variables locales locAmt et remAmt.
Pour chacun de ceux-ci correspondant à clauses, je vais de manière récursive invoquer concilierinterne. La principale différence, c'est ce que je fais avec la liste d'outputSoFar avant que je le répète. Si le locAmt et le remAmt sont les mêmes, c'est un match, donc j'ai ajouter une nouvelle RegEntry dans la liste d'outputSoFar avant recursing. Dans les autres cas, j'ai juste les ignorer et recurse. Le résultat sera une liste d'éléments de RegEntry, et c'est ce qui est retourné à l'appelant.
Étendre l'idée
Supposons que je ne peux pas simplement ignorer ces éléments ne correspondent pas. J'ai besoin de mettre un élément dans la liste résultante qui dit que c'était une transaction locale inégalée ou une transaction à distance inégalée. L'algorithme principal soit toujours valable, j'ai juste ajouter de nouveaux éléments dans le registre de l'union de tenir chacune de ces possibilités et les ajouter dans la liste avant recursing, fait preuve de discrimination, comme le montre Figure 3.
Figure 3 ajouter de nouveaux éléments au registre
type Register =
| RegEntry of Transaction * Transaction
| MissingRemote of Transaction
| MissingLocal of Transaction
let reconcile (local : Transaction list)
(remote : Transaction list) : Register list =
let rec reconcileInternal outputSoFar local remote =
match (local, remote) with
| [], _
| _, [] -> outputSoFar
| loc :: locTail, rem :: remTail ->
match (loc.amount, rem.amount) with
| (locAmt, remAmt) when locAmt = remAmt ->
reconcileInternal (RegEntry(loc, rem) :: outputSoFar) locTail remTail
| (locAmt, remAmt) when locAmt < remAmt ->
reconcileInternal (MissingRemote(loc) :: outputSoFar) locTail remote
| (locAmt, remAmt) when locAmt > remAmt ->
reconcileInternal (MissingLocal(rem) :: outputSoFar) local remTail
| _ ->
failwith "How is this possible?"
reconcileInternal [] local remote
Maintenant, le résultat sera une liste complète, avec des entrées MissingLocal ou MissingRemote pour chaque Transaction qui n'a une paire correspondante. En fait, ce n'est pas tout à fait vrai. Si les deux listes ne correspondent pas en longueur, comme mon cas test2 plus tôt, les éléments restants ne bénéficier d'entrées « Manquant ».
En prenant F # comme le « conceptualisation » de la langue au lieu de c# et utilisant les principes de programmation fonctionnelles, c'est devenu une solution assez rapide. F # utilise l'inférence de type étendu, dans bien des cas tout en développant le code, je n'avais donc pas déterminer les types réels pour les paramètres et retourner à l'avance. Les fonctions récursives dans F # devra souvent une annotation de type pour définir le type de retour. J'ai s'est enfui sans il ici parce qu'il pouvait déduire le retour type donnée sur le cercle extérieur, enfermant la fonction.
Dans certains cas, je pourrais juste cela compiler dans un assembly et déposez-le aux développeurs c#. Pour beaucoup de magasins, cependant, cela ne va pas voler. Donc la prochaine fois, je vais convertir cette à c#. Le patron ne saura jamais que ce code a en fait commencé la vie comme code F #.
bon codage !
Ted Neward est le CTO chez iTrellis, une firme de services. Auteur de plus de 100 articles, il a rédigé et corédigé plus d'une dizaine d'ouvrages, y compris « Professional F# 2.0 » (Wrox, 2010). Il est MVP C# et participe à des conférences dans le monde entier. Il consulte et mentors régulièrement — le joindre à ted@tedneward.com ou ted@itrellis.com si vous êtes intéressé à lui avoir viennent travailler avec votre équipe et consulter son blog à blogs.tedneward.com.
Merci à l'expert technique Microsoft suivant d'avoir relu cet article : Lincoln Atkinson