La compréhension de la correspondance de modèle nécessite d'expliquer en trois parties:
- Types de données algébriques.
- Ce filtrage est
- Pourquoi c'est génial.
Types de données algébriques en un mot
ML-comme les langages fonctionnels vous permettent de définir des types de données simples appelé "disjoints des syndicats" ou "types de données algébriques". Ces structures de données sont de simples conteneurs, et peut être définie de manière récursive. Par exemple:
type 'a list =
| Nil
| Cons of 'a * 'a list
définit une pile comme structure de données. Il pense que l'équivalent de ce C#:
public abstract class List<T>
{
public class Nil : List<T> { }
public class Cons : List<T>
{
public readonly T Item1;
public readonly List<T> Item2;
public Cons(T item1, List<T> item2)
{
this.Item1 = item1;
this.Item2 = item2;
}
}
}
Ainsi, l' Cons
et Nil
identifiants définir simple d'une classe simple, où l' of x * y * z * ...
définit un constructeur et certains types de données. Les paramètres du constructeur sont sans nom, ils sont identifiés par la position et le type de données.
Vous créez des instances de votre a list
de la classe en tant que telle:
let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
Qui est le même que:
Stack<int> x = new Cons(1, new Cons(2, new Cons(3, new Cons(4, new Nil()))));
La correspondance de motif dans un mot
Le Pattern matching est une sorte d'essais de type. Donc, disons que nous avons créé une pile d'objet comme celui ci-dessus, nous pouvons mettre en œuvre des méthodes de peek et de la pop la pile comme suit:
let peek s =
match s with
| Cons(hd, tl) -> hd
| Nil -> failwith "Empty stack"
let pop s =
match s with
| Cons(hd, tl) -> tl
| Nil -> failwith "Empty stack"
Les méthodes ci-dessus sont équivalentes (bien que n'étant pas mis en œuvre en tant que telle) à la suite de C#:
public static T Peek<T>(Stack<T> s)
{
if (s is Stack<T>.Cons)
{
T hd = ((Stack<T>.Cons)s).Item1;
Stack<T> tl = ((Stack<T>.Cons)s).Item2;
return hd;
}
else if (s is Stack<T>.Nil)
throw new Exception("Empty stack");
else
throw new MatchFailureException();
}
public static Stack<T> Pop<T>(Stack<T> s)
{
if (s is Stack<T>.Cons)
{
T hd = ((Stack<T>.Cons)s).Item1;
Stack<T> tl = ((Stack<T>.Cons)s).Item2;
return tl;
}
else if (s is Stack<T>.Nil)
throw new Exception("Empty stack");
else
throw new MatchFailureException();
}
(Presque toujours, ML langues implémenter le pattern matching sans de type à l'exécution de tests ou de moulages, de sorte que le code C# est quelque peu trompeur. Laissez la brosse détails de mise en œuvre de côté avec quelques à la main en agitant s'il vous plaît :) )
La structure de données de décomposition en un mot
Ok, revenons à la méthode lire:
let peek s =
match s with
| Cons(hd, tl) -> hd
| Nil -> failwith "Empty stack"
Le truc est de comprendre que l' hd
et tl
identificateurs de variables (errm... car ils sont immuables, ils ne sont pas vraiment des "variables", mais "valeurs" ;) ). Si s
type Cons
, puis nous allons sortir de ses valeurs, de constructeur et de les lier à des variables nommées hd
et tl
.
Le Pattern matching est utile, car elle nous permet de décomposer une structure de données par sa forme à la place de son contenu. Alors imaginez si nous définissons un arbre binaire comme suit:
type 'a tree =
| Node of 'a tree * 'a * 'a tree
| Nil
Nous pouvons définir quelques rotations de l'arbre comme suit:
let rotateLeft = function
| Node(a, p, Node(b, q, c)) -> Node(Node(a, p, b), q, c)
| x -> x
let rotateRight = function
| Node(Node(a, p, b), q, c) -> Node(a, p, Node(b, q, c))
| x -> x
( let rotateRight = function
Constructeur est la syntaxe de sucre pour let rotateRight s = match s with ...
.)
Donc, en plus de la liaison de la structure de données pour les variables, on peut aussi descendre en elle. Disons que nous avons un nœud let x = Node(Nil, 1, Nil)
. Si nous appelons rotateLeft x
, nous testons x
contre le premier modèle, qui ne parvient pas à égaler, parce que le droit de l'enfant de type Nil
au lieu de Node
. Il se déplacera pour le motif suivant, x -> x
, qui correspondent à n'importe quelle entrée et de le retourner sans modification.
Pour comparaison, nous aimerions écrire les méthodes ci-dessus en C#:
public abstract class Tree<T>
{
public abstract U Match<U>(Func<U> nilFunc, Func<Tree<T>, T, Tree<T>, U> nodeFunc);
public class Nil : Tree<T>
{
public override U Match<U>(Func<U> nilFunc, Func<Tree<T>, T, Tree<T>, U> nodeFunc)
{
return nilFunc();
}
}
public class Node : Tree<T>
{
readonly Tree<T> Left;
readonly T Value;
readonly Tree<T> Right;
public Node(Tree<T> left, T value, Tree<T> right)
{
this.Left = left;
this.Value = value;
this.Right = right;
}
public override U Match<U>(Func<U> nilFunc, Func<Tree<T>, T, Tree<T>, U> nodeFunc)
{
return nodeFunc(Left, Value, Right);
}
}
public static Tree<T> RotateLeft(Tree<T> t)
{
return t.Match(
() => t,
(l, x, r) => r.Match(
() => t,
(rl, rx, rr) => new Node(new Node(l, x, rl), rx, rr))));
}
public static Tree<T> RotateRight(Tree<T> t)
{
return t.Match(
() => t,
(l, x, r) => l.Match(
() => t,
(ll, lx, lr) => new Node(ll, lx, new Node(lr, x, r))));
}
}
Pour au sérieux.
Le Pattern matching est génial
Vous pouvez mettre en place quelque chose de similaire à la correspondance de motif en C# à l'aide du modèle visiteur, mais ce n'est pas près d'aussi flexible car vous ne pouvez pas effectivement décomposer les structures de données complexes. En outre, si vous utilisez le filtrage, le compilateur va vous dire si vous avez quitté un cas. C'est pas génial?
Pensez à comment vous pouvez mettre en œuvre une fonctionnalité similaire en C# ou en langues, sans filtrage. Pensez à comment vous pouvez le faire sans test-tests et les jette au moment de l'exécution. Son certainement pas dur, juste lourd et encombrant. Et vous n'avez pas le compilateur de vérifier que vous avez couvert tous les cas.
Afin de filtrage vous permet de décomposer et de naviguer dans des structures de données dans un emplacement très pratique, syntaxe compacte, il permet au compilateur de vérifier la logique de votre code, au moins un peu. Il vraiment est une killer feature.