Desen eşleştirmesini anlamak, üç bölümün açıklanmasını gerektirir:
- Cebirsel veri türleri.
- Desen eşleştirme nedir
- Neden harika.
Özetle cebirsel veri türleri
ML benzeri işlevsel diller, "ayrık birleşimler" veya "cebirsel veri türleri" adı verilen basit veri türlerini tanımlamanıza olanak tanır. Bu veri yapıları basit kaplardır ve özyinelemeli olarak tanımlanabilir. Örneğin:
type 'a list =
| Nil
| Cons of 'a * 'a list
yığın benzeri bir veri yapısı tanımlar. Bunu bu C # ile eşdeğer olarak düşünün:
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;
}
}
}
Bu nedenle, Cons
ve Nil
tanımlayıcılar basit bir yapıcıyı of x * y * z * ...
ve bazı veri türlerini tanımlayan basit bir sınıfı tanımlar. Yapıcıya giden parametreler isimsizdir, konum ve veri türüne göre tanımlanırlar.
a list
Sınıfınızın örneklerini şu şekilde oluşturursunuz :
let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
Aşağıdakilerle aynıdır:
Stack<int> x = new Cons(1, new Cons(2, new Cons(3, new Cons(4, new Nil()))));
Özetle desen eşleştirme
Örüntü eşleştirme bir tür tip testidir. Diyelim ki yukarıdaki gibi bir yığın nesnesi oluşturduk, yığını gözetlemek ve açmak için aşağıdaki gibi yöntemler uygulayabiliriz:
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"
Yukarıdaki yöntemler, aşağıdaki C # ile eşdeğerdir (bu şekilde uygulanmamasına rağmen):
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();
}
(Neredeyse her zaman, makine öğrenimi dilleri çalışma zamanı tip testleri veya yayınlar olmadan kalıp eşleştirmesi uygular , bu nedenle C # kodu biraz aldatıcıdır. Uygulama ayrıntılarını biraz el sallayarak bir kenara bırakalım lütfen :))
Özetle veri yapısı ayrıştırması
Tamam, gözetleme yöntemine geri dönelim:
let peek s =
match s with
| Cons(hd, tl) -> hd
| Nil -> failwith "Empty stack"
İşin püf noktası hd
ve tl
tanımlayıcıların değişkenler olduğunu anlamaktır (errm ... değişmez olduklarından, gerçekte "değişkenler" değiller, "değerler";)). Eğer s
türü vardır Cons
, o zaman yapıcı dışına değerlerini sökeceksin ve bağlama onları adlı değişkenlere ediyoruz hd
ve tl
.
Desen eşleştirme yararlıdır çünkü bir veri yapısını içeriği yerine şekline göre ayrıştırmamıza izin verir . Bir ikili ağacı aşağıdaki gibi tanımladığımızı hayal edin:
type 'a tree =
| Node of 'a tree * 'a * 'a tree
| Nil
Bazı ağaç dönüşlerini şu şekilde tanımlayabiliriz :
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
Yapıcı, sözdizimi şekeridir let rotateRight s = match s with ...
.)
Bu nedenle, veri yapısını değişkenlere bağlamanın yanı sıra, onu ayrıntılı olarak inceleyebiliriz. Diyelim ki bir düğümümüz var let x = Node(Nil, 1, Nil)
. Dediğimiz takdirde rotateLeft x
, test ettiğimiz x
doğru çocuk türüne sahip olduğundan eşleştirememişse ilk desen, karşı Nil
yerine Node
. x -> x
Herhangi bir girdi ile eşleşecek ve onu değiştirilmemiş olarak döndürecek olan bir sonraki modele geçecektir.
Karşılaştırma için yukarıdaki yöntemleri C # ile şöyle yazardık:
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))));
}
}
Cidden.
Desen eşleştirme harika
Ziyaretçi desenini kullanarak C # 'da desen eşleştirmeye benzer bir şey uygulayabilirsiniz , ancak karmaşık veri yapılarını etkili bir şekilde ayrıştıramayacağınız için neredeyse esnek değildir. Dahası, desen eşleştirmeyi kullanıyorsanız , derleyici size bir vakayı bırakıp bırakmadığınızı söyleyecektir . Bu ne kadar harika?
C # veya kalıp eşleştirmesi olmayan dillerde benzer işlevleri nasıl uygulayacağınızı düşünün. Çalışma zamanında test testleri ve yayınlar olmadan bunu nasıl yapacağınızı düşünün. Kesinlikle zor değil , sadece hantal ve hantal. Ve her vakayı ele aldığınızdan emin olmak için derleyiciyi kontrol etmezsiniz.
Bu nedenle desen eşleştirme, veri yapılarını çok uygun, kompakt bir sözdiziminde ayrıştırmanıza ve gezinmenize yardımcı olur, derleyicinin kodunuzun mantığını en azından biraz kontrol etmesini sağlar . Gerçekten olduğunu bir katil özelliği.