Понимание шаблону требует объяснений трех частей:
- Алгебраических типов данных.
- Что соответствует шаблону
- Почему это потрясающе.
Алгебраические типы данных в ореховой скорлупе
ML-как функциональные языки позволяют определять простые типы данных, называемые «непересекающиеся объединения» или «алгебраические типы данных». Эти структуры данных являются простыми контейнерами и могут быть рекурсивно определены. Например:
type 'a list =
| Nil
| Cons of 'a * 'a list
определяет структуру данных, подобную стеку.Думайте об этом как эквивалент этой 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;
}
}
}
Таким образом, идентификаторы Cons
и Nil
определяют простой простой класс, где of x * y * z * ...
определяет конструктор и некоторые типы данных. Параметры конструктора не обозначены, они идентифицируются по положению и типу данных.
создаются экземпляры вашего a list
класса как таковые:
let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
Что такое же, как:
Stack<int> x = new Cons(1, new Cons(2, new Cons(3, new Cons(4, new Nil()))));
сопоставление с образцом в миниатюре
соответствия шаблона является своим родом типового тестирования. Так что давайте говорить, что мы создали объект стека, как один выше, мы можем реализовать методы PEEK и поп стека следующим образом:
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"
методы являются эквивалентом (хотя и не реализован как таковой) к следующему 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();
}
(почти всегда, ML языки реализовать шаблон без времени выполнения типовых тестов или слепков, так что C# коды несколько обманчивы. Давай деталь реализации отмахнуться с некоторыми ручными махнув пожалуйста :))
разложения структуры данных в скорлупе
Хорошо, давайте вернемся к методу пряток:
let peek s =
match s with
| Cons(hd, tl) -> hd
| Nil -> failwith "Empty stack"
Уловка понимание, что идентификаторы hd
и tl
являются переменными (errm ... так как они «неизменяемы, они не являются« переменными », а« значениями »;)). Если s
имеет тип Cons
, то мы собираемся вытащить его значения из конструктора и привязать их к переменным с именем hd
и tl
.
соответствие шаблона является полезным, поскольку оно позволяет нам разложить структуру данных по своей форме вместо его содержания. Итак, представьте, если мы определим бинарное дерево следующим образом:
type 'a tree =
| Node of 'a tree * 'a * 'a tree
| Nil
Мы можем определить некоторые tree rotations следующим образом: (. let rotateRight = function
конструктор синтаксисом для let rotateRight s = match s with ...
)
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 x = Node(Nil, 1, Nil)
. Если мы позвоним rotateLeft x
, мы протестируем x
против первого шаблона, который не соответствует, потому что правый ребенок имеет тип Nil
вместо Node
. Он перейдет к следующему шаблону, x -> x
, который будет соответствовать любому входу и вернуть его без изменений.
Для сравнения, мы бы написать методы выше в 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))));
}
}
Для серьезно.
соответствие шаблона является удивительным
Вы можете реализовать что-то подобное к шаблону в C# с использованием visitor pattern, но не столь гибким, потому что вы не можете эффективно разлагать сложные структуры данных. Более того, если вы используете сопоставление с образцом, компилятор скажет вам, если вы упустили случай. Как это здорово?
Подумайте, как реализовать аналогичную функциональность на C# или языках без соответствия шаблону. Подумайте о том, как вы это сделаете без тестовых тестов и бросков во время выполнения. Его, конечно, не твердый, просто громоздкий и громоздкий. И у вас нет проверки компилятора, чтобы убедиться, что вы охватили все случаи.
Таким образом, сопоставление шаблонов помогает разложить и перемещать структуры данных в очень удобном компактном синтаксисе, что позволяет компилятору проверить логику вашего кода, по крайней мере, немного. Это действительно функция убийцы.
Возможный дубликат [Совместимость шаблонов Haskell - что это?] (Http://stackoverflow.com/questions/2225774/haskell-pattern-matching-what-is-it) – nawfal