ฉันกำลังอ่านเกี่ยวกับการเขียนโปรแกรมเชิงฟังก์ชันและฉันสังเกตเห็นว่าการจับคู่รูปแบบมีการกล่าวถึงในหลายบทความว่าเป็นหนึ่งในคุณสมบัติหลักของภาษาที่ใช้งานได้
ใครช่วยอธิบายสำหรับนักพัฒนา Java / C ++ / JavaScript ว่าหมายความว่าอย่างไร
ฉันกำลังอ่านเกี่ยวกับการเขียนโปรแกรมเชิงฟังก์ชันและฉันสังเกตเห็นว่าการจับคู่รูปแบบมีการกล่าวถึงในหลายบทความว่าเป็นหนึ่งในคุณสมบัติหลักของภาษาที่ใช้งานได้
ใครช่วยอธิบายสำหรับนักพัฒนา Java / C ++ / JavaScript ว่าหมายความว่าอย่างไร
คำตอบ:
การทำความเข้าใจการจับคู่รูปแบบจำเป็นต้องอธิบายสามส่วน:
ประเภทข้อมูลพีชคณิตโดยสรุป
ภาษาที่ใช้งานได้เหมือน ML ช่วยให้คุณกำหนดประเภทข้อมูลง่ายๆที่เรียกว่า "disjoint unions" หรือ "algebraic data types" โครงสร้างข้อมูลเหล่านี้เป็นคอนเทนเนอร์อย่างง่ายและสามารถกำหนดซ้ำได้ ตัวอย่างเช่น:
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()))));
การจับคู่รูปแบบโดยสรุป
การจับคู่รูปแบบเป็นการทดสอบประเภทหนึ่ง สมมติว่าเราสร้างสแต็กอ็อบเจ็กต์ขึ้นมาเช่นเดียวกับด้านบนเราสามารถใช้เมธอดเพื่อดูและป๊อปสแต็กได้ดังนี้:
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
ตัวระบุเป็นตัวแปร (เอ่อ ... เนื่องจากไม่เปลี่ยนรูปจึงไม่ใช่ "ตัวแปร" แต่เป็น "ค่า";)) หากs
มีประเภทCons
เราจะดึงค่าออกจากตัวสร้างและผูกเข้ากับตัวแปรที่ชื่อhd
และtl
และ
จับคู่รูปแบบจะเป็นประโยชน์เพราะมันจะช่วยให้เราย่อยสลายโครงสร้างข้อมูลโดยตัวของมันรูปร่างแทนของเนื้อหา ลองนึกดูว่าถ้าเรากำหนดต้นไม้ไบนารีดังนี้:
type 'a tree =
| Node of 'a tree * 'a * 'a tree
| Nil
เราสามารถกำหนดการหมุนเวียนของต้นไม้ได้ดังนี้:
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
สร้างคือน้ำตาลไวยากรณ์สำหรับlet rotateRight s = match s with ...
)
ดังนั้นนอกจากการผูกโครงสร้างข้อมูลกับตัวแปรแล้วเรายังสามารถเจาะลึกลงไปได้อีกด้วย 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 # โดยใช้รูปแบบผู้เยี่ยมชมแต่ไม่ยืดหยุ่นเกือบเท่าเพราะคุณไม่สามารถย่อยสลายโครงสร้างข้อมูลที่ซับซ้อนได้อย่างมีประสิทธิภาพ ยิ่งไปกว่านั้นหากคุณใช้การจับคู่รูปแบบคอมไพเลอร์จะบอกคุณว่าคุณทิ้งเคสไว้หรือไม่ไว้หรือไม่ จะน่ากลัวขนาดไหน
ลองนึกถึงวิธีที่คุณใช้ฟังก์ชันที่คล้ายกันในภาษา C # หรือภาษาที่ไม่มีรูปแบบที่ตรงกัน ลองนึกถึงวิธีที่คุณทำได้โดยไม่ต้องทดสอบการทดสอบและแคสต์ที่รันไทม์ มันไม่ยากอย่างแน่นอนเพียงแค่ยุ่งยากและใหญ่โต และคุณไม่มีการตรวจสอบคอมไพเลอร์เพื่อให้แน่ใจว่าคุณได้ครอบคลุมทุกกรณี
ดังนั้นการจับคู่รูปแบบจะช่วยให้คุณแยกย่อยและนำทางโครงสร้างข้อมูลในรูปแบบที่สะดวกและกะทัดรัดช่วยให้คอมไพเลอร์สามารถตรวจสอบตรรกะของโค้ดของคุณได้อย่างน้อยก็เล็กน้อย มันเป็นคุณสมบัติของนักฆ่าจริงๆ
คำตอบสั้น ๆ : การจับคู่รูปแบบเกิดขึ้นเนื่องจากภาษาที่ใช้งานได้ถือว่าเครื่องหมายเท่ากับเป็นการยืนยันความเท่าเทียมกันแทนการกำหนด
คำตอบแบบยาว: การจับคู่รูปแบบเป็นรูปแบบของการจัดส่งตาม "รูปร่าง" ของค่าที่กำหนด ในภาษาที่ใช้งานได้ประเภทข้อมูลที่คุณกำหนดมักเป็นสิ่งที่เรียกว่าสหภาพแรงงานที่เลือกปฏิบัติหรือประเภทข้อมูลเกี่ยวกับพีชคณิต ตัวอย่างเช่นรายการ (เชื่อมโยง) คืออะไร? รายการที่เชื่อมโยงList
ของบางสิ่งบางประเภทa
อาจเป็นรายการว่างNil
หรือองค์ประกอบบางส่วนของประเภทa
Cons
ed ลงในList a
(รายการa
s) ใน Haskell (ภาษาที่ใช้งานได้ฉันคุ้นเคยมากที่สุด) เราเขียนสิ่งนี้
data List a = Nil
| Cons a (List a)
สหภาพแรงงานที่เลือกปฏิบัติทั้งหมดถูกกำหนดด้วยวิธีนี้: ประเภทเดียวมีจำนวนคงที่ของวิธีต่างๆในการสร้าง ผู้สร้างเช่นNil
และCons
ที่นี่เรียกว่าผู้สร้าง ซึ่งหมายความว่าค่าของประเภทList a
สามารถถูกสร้างขึ้นด้วยตัวสร้างที่แตกต่างกันสองตัวซึ่งอาจมีรูปร่างที่แตกต่างกันสองแบบ สมมติว่าเราต้องการเขียนhead
ฟังก์ชันเพื่อรับองค์ประกอบแรกของรายการ ใน Haskell เราจะเขียนสิ่งนี้เป็น
-- `head` is a function from a `List a` to an `a`.
head :: List a -> a
-- An empty list has no first item, so we raise an error.
head Nil = error "empty list"
-- If we are given a `Cons`, we only want the first part; that's the list's head.
head (Cons h _) = h
เนื่องจากList a
ค่าสามารถมีได้สองประเภทที่แตกต่างกันเราจึงต้องจัดการแต่ละค่าแยกกัน นี่คือการจับคู่รูปแบบ ในhead x
ถ้าx
ตรงกับรูปแบบNil
เราจะเรียกใช้กรณีแรก ถ้าตรงกับรูปแบบCons h _
เราจะเรียกใช้อันที่สอง
คำตอบสั้น ๆ อธิบาย:ฉันคิดว่าหนึ่งในวิธีที่ดีที่สุดในการคิดเกี่ยวกับพฤติกรรมนี้คือการเปลี่ยนวิธีคิดของเครื่องหมายเท่ากับ ในภาษาหยิกวงเล็บโดยและขนาดใหญ่=
หมายถึงการได้รับมอบหมาย: a = b
หมายถึง“ให้a
เข้าb
.” ในจำนวนมากของภาษาการทำงาน แต่=
หมายถึงการยืนยันของความเสมอภาค: let Cons a (Cons b Nil) = frob x
อ้างว่าสิ่งที่อยู่ทางด้านซ้ายที่Cons a (Cons b Nil)
เทียบเท่ากับสิ่งที่ทางด้านขวาที่frob x
; นอกจากนี้ตัวแปรทั้งหมดที่ใช้ทางด้านซ้ายจะปรากฏให้เห็น นี่คือสิ่งที่เกิดขึ้นกับอาร์กิวเมนต์ของฟังก์ชัน: เรายืนยันว่าอาร์กิวเมนต์แรกดูเหมือนNil
และถ้าไม่เป็นเช่นนั้นเราจะตรวจสอบต่อไป
Cons
หมายถึง?
Cons
เป็นผู้รักษาข้อเสียที่สร้างรายการ (เชื่อมโยง) จากส่วนหัว (the a
) และส่วนหาง (the List a
) ชื่อนี้มาจาก Lisp ใน Haskell สำหรับประเภทรายการในตัวจะเป็นตัว:
ดำเนินการ (ซึ่งยังคงออกเสียงว่า "ข้อเสีย")
มันหมายความว่าแทนที่จะเขียน
double f(int x, int y) {
if (y == 0) {
if (x == 0)
return NaN;
else if (x > 0)
return Infinity;
else
return -Infinity;
} else
return (double)x / y;
}
คุณสามารถเขียน
f(0, 0) = NaN;
f(x, 0) | x > 0 = Infinity;
| else = -Infinity;
f(x, y) = (double)x / y;
เฮ้ C ++ รองรับการจับคู่รูปแบบด้วย
static const int PositiveInfinity = -1;
static const int NegativeInfinity = -2;
static const int NaN = -3;
template <int x, int y> struct Divide {
enum { value = x / y };
};
template <bool x_gt_0> struct aux { enum { value = PositiveInfinity }; };
template <> struct aux<false> { enum { value = NegativeInfinity }; };
template <int x> struct Divide<x, 0> {
enum { value = aux<(x>0)>::value };
};
template <> struct Divide<0, 0> {
enum { value = NaN };
};
#include <cstdio>
int main () {
printf("%d %d %d %d\n", Divide<7,2>::value, Divide<1,0>::value, Divide<0,0>::value, Divide<-1,0>::value);
return 0;
};
การจับคู่รูปแบบเป็นเหมือนวิธีการที่ใช้สเตียรอยด์มากเกินไป กรณีที่ง่ายที่สุดจะเหมือนกับที่คุณเห็นใน java อาร์กิวเมนต์คือรายการประเภทที่มีชื่อ วิธีการเรียกที่ถูกต้องขึ้นอยู่กับอาร์กิวเมนต์ที่ส่งเข้ามาและจะเพิ่มเป็นสองเท่าเป็นการกำหนดอาร์กิวเมนต์เหล่านั้นให้กับชื่อพารามิเตอร์
รูปแบบเพียงแค่ก้าวไปอีกขั้นและสามารถทำลายข้อโต้แย้งที่ส่งผ่านไปได้ไกลขึ้น นอกจากนี้ยังสามารถใช้ยามเพื่อจับคู่ตามมูลค่าของอาร์กิวเมนต์ได้ เพื่อสาธิตฉันจะแสร้งทำเป็นว่า JavaScript มีการจับคู่รูปแบบ
function foo(a,b,c){} //no pattern matching, just a list of arguments
function foo2([a],{prop1:d,prop2:e}, 35){} //invented pattern matching in JavaScript
ใน foo2 คาดว่า a จะเป็นอาร์เรย์โดยจะแยกอาร์กิวเมนต์ที่สองออกจากกันโดยคาดว่าอ็อบเจ็กต์ที่มีอุปกรณ์ประกอบฉากสองตัว (prop1, prop2) และกำหนดค่าของคุณสมบัติเหล่านั้นให้กับตัวแปร d และ e จากนั้นคาดว่าอาร์กิวเมนต์ที่สามจะเป็น 35
ซึ่งแตกต่างจาก JavaScript ตรงที่ภาษาที่มีการจับคู่รูปแบบมักจะอนุญาตหลายฟังก์ชันที่มีชื่อเดียวกัน แต่มีรูปแบบที่แตกต่างกัน ด้วยวิธีนี้ก็เหมือนกับวิธีการโอเวอร์โหลด ฉันจะยกตัวอย่างใน erlang:
fibo(0) -> 0 ;
fibo(1) -> 1 ;
fibo(N) when N > 0 -> fibo(N-1) + fibo(N-2) .
เบลอตาของคุณเล็กน้อยและคุณสามารถจินตนาการได้ในจาวาสคริปต์ สิ่งนี้อาจจะ:
function fibo(0){return 0;}
function fibo(1){return 1;}
function fibo(N) when N > 0 {return fibo(N-1) + fibo(N-2);}
ชี้ให้เห็นว่าเมื่อคุณเรียก fibo การใช้งานจะขึ้นอยู่กับอาร์กิวเมนต์ แต่ในกรณีที่ Java ถูก จำกัด ไว้ที่ประเภทเป็นวิธีเดียวในการโอเวอร์โหลดการจับคู่รูปแบบสามารถทำได้มากกว่า
นอกเหนือจากฟังก์ชั่นการโอเวอร์โหลดดังที่แสดงไว้ที่นี่แล้วหลักการเดียวกันนี้ยังสามารถนำไปใช้กับที่อื่น ๆ ได้เช่นคำสั่งกรณีหรือการกำหนดโครงสร้าง JavaScript ยังมีใน 1.7
การจับคู่รูปแบบช่วยให้คุณสามารถจับคู่ค่า (หรือวัตถุ) กับรูปแบบบางอย่างเพื่อเลือกสาขาของรหัส จากมุมมองของ C ++ อาจฟังดูคล้ายกับswitch
คำสั่งเล็กน้อย ในภาษาที่ใช้งานได้การจับคู่รูปแบบสามารถใช้สำหรับการจับคู่กับค่าดั้งเดิมมาตรฐานเช่นจำนวนเต็ม อย่างไรก็ตามมีประโยชน์มากกว่าสำหรับประเภทประกอบ
ก่อนอื่นมาสาธิตการจับคู่รูปแบบกับค่าดั้งเดิม (โดยใช้ pseudo-C ++ เพิ่มเติมswitch
):
switch(num) {
case 1:
// runs this when num == 1
case n when n > 10:
// runs this when num > 10
case _:
// runs this for all other cases (underscore means 'match all')
}
ข้อเสนอที่สองใช้กับชนิดข้อมูลทำงานเช่นtuples (ซึ่งช่วยให้คุณสามารถจัดเก็บวัตถุหลายในค่าเดียว) และสหภาพการเลือกปฏิบัติที่ช่วยให้คุณสร้างชนิดที่สามารถมีหนึ่งในหลายตัวเลือก ดูเหมือนenum
ว่าแต่ละป้ายจะมีค่าบางอย่าง ในไวยากรณ์หลอก C ++:
enum Shape {
Rectangle of { int left, int top, int width, int height }
Circle of { int x, int y, int radius }
}
Shape
ตอนนี้ค่าของประเภทสามารถมีRectangle
พิกัดทั้งหมดหรือCircle
มีจุดศูนย์กลางและรัศมี การจับคู่รูปแบบช่วยให้คุณสามารถเขียนฟังก์ชันสำหรับการทำงานกับShape
ประเภท:
switch(shape) {
case Rectangle(l, t, w, h):
// declares variables l, t, w, h and assigns properties
// of the rectangle value to the new variables
case Circle(x, y, r):
// this branch is run for circles (properties are assigned to variables)
}
สุดท้ายคุณยังสามารถใช้รูปแบบซ้อนที่รวมคุณสมบัติทั้งสองเข้าด้วยกัน ตัวอย่างเช่นคุณสามารถใช้Circle(0, 0, radius)
เพื่อจับคู่กับรูปร่างทั้งหมดที่มีจุดศูนย์กลางอยู่ในจุด [0, 0] และมีรัศมีใด ๆ ก็ได้ (ค่าของรัศมีจะถูกกำหนดให้กับตัวแปรใหม่radius
)
สิ่งนี้อาจฟังดูไม่คุ้นเคยเล็กน้อยจากมุมมอง C ++ แต่ฉันหวังว่า C ++ หลอกของฉันจะทำให้คำอธิบายชัดเจน การเขียนโปรแกรมเชิงฟังก์ชันขึ้นอยู่กับแนวคิดที่แตกต่างกันมากดังนั้นจึงมีความหมายที่ดีกว่าในภาษาที่ใช้งานได้!
การจับคู่รูปแบบเป็นที่ที่ล่ามสำหรับภาษาของคุณจะเลือกฟังก์ชันเฉพาะโดยพิจารณาจากโครงสร้างและเนื้อหาของอาร์กิวเมนต์ที่คุณให้ไว้
ไม่เพียง แต่เป็นคุณสมบัติภาษาที่ใช้งานได้เท่านั้น แต่ยังมีให้สำหรับภาษาต่างๆอีกมากมาย
ครั้งแรกที่ฉันได้พบกับความคิดนี้คือเมื่อฉันได้เรียนรู้ prolog ซึ่งเป็นศูนย์กลางของภาษาจริงๆ
เช่น
สุดท้าย ([LastItem], LastItem)
last ([Head | Tail], LastItem): - last (Tail, LastItem)
รหัสด้านบนจะให้รายการสุดท้ายของรายการ อินพุต arg เป็นครั้งแรกและผลลัพธ์คือวินาที
หากมีเพียงรายการเดียวในรายการล่ามจะเลือกเวอร์ชันแรกและอาร์กิวเมนต์ที่สองจะถูกกำหนดให้เท่ากับค่าแรกคือค่าจะถูกกำหนดให้กับผลลัพธ์
หากรายการมีทั้งส่วนหัวและส่วนท้ายล่ามจะเลือกเวอร์ชันที่สองและเรียกคืนจนกว่าจะเหลือเพียงรายการเดียวในรายการ
สำหรับหลาย ๆ คนการหยิบแนวคิดใหม่จะง่ายกว่าหากมีตัวอย่างง่ายๆดังนั้นเราไปที่นี่:
สมมติว่าคุณมีรายการจำนวนเต็มสามตัวและต้องการเพิ่มองค์ประกอบแรกและองค์ประกอบที่สาม หากไม่มีการจับคู่รูปแบบคุณสามารถทำได้เช่นนี้ (ตัวอย่างใน Haskell):
Prelude> let is = [1,2,3]
Prelude> head is + is !! 2
4
ตอนนี้แม้ว่านี่จะเป็นตัวอย่างของเล่น แต่ลองนึกภาพว่าเราต้องการผูกจำนวนเต็มแรกและสามเข้ากับตัวแปรและรวมเข้าด้วยกัน:
addFirstAndThird is =
let first = head is
third = is !! 3
in first + third
การแยกค่าจากโครงสร้างข้อมูลนี้เป็นการจับคู่รูปแบบ โดยพื้นฐานแล้วคุณ "สะท้อน" โครงสร้างของบางสิ่งโดยให้ตัวแปรเชื่อมโยงกับสถานที่ที่น่าสนใจ:
addFirstAndThird [first,_,third] = first + third
เมื่อคุณเรียกใช้ฟังก์ชันนี้โดยมี [1,2,3] เป็นอาร์กิวเมนต์ [1,2,3] จะรวมเข้าด้วยกันกับ [แรก_
, สาม], การเชื่อมโยงครั้งแรกกับ 1, สามถึง 3 และทิ้ง 2 ( _
เป็นตัวยึด สำหรับสิ่งที่คุณไม่สนใจ)
ตอนนี้หากคุณต้องการจับคู่รายการที่มี 2 เป็นองค์ประกอบที่สองคุณสามารถทำได้ดังนี้:
addFirstAndThird [first,2,third] = first + third
สิ่งนี้จะใช้ได้เฉพาะกับรายการที่มี 2 เป็นองค์ประกอบที่สองเท่านั้นและจะมีข้อยกเว้นเป็นอย่างอื่นเนื่องจากไม่มีการกำหนดคำจำกัดความสำหรับ addFirstAndThird สำหรับรายการที่ไม่ตรงกัน
จนถึงตอนนี้เราใช้การจับคู่รูปแบบสำหรับการผูกแบบทำลายโครงสร้างเท่านั้น เหนือสิ่งนั้นคุณสามารถให้คำจำกัดความของฟังก์ชันเดียวกันได้หลายคำโดยใช้คำจำกัดความการจับคู่แรกดังนั้นการจับคู่รูปแบบจึงคล้ายกับ "คำสั่งสวิตช์บนสเตอริโอ":
addFirstAndThird [first,2,third] = first + third
addFirstAndThird _ = 0
addFirstAndThird จะเพิ่มองค์ประกอบที่หนึ่งและสามของรายการโดยมี 2 เป็นองค์ประกอบที่สองอย่างมีความสุขมิฉะนั้นจะ "ผ่าน" และ "ส่งคืน" 0 ฟังก์ชัน "เหมือนสวิตช์" นี้ไม่เพียง แต่สามารถใช้ในข้อกำหนดฟังก์ชันเท่านั้นเช่น:
Prelude> case [1,3,3] of [a,2,c] -> a+c; _ -> 0
0
Prelude> case [1,2,3] of [a,2,c] -> a+c; _ -> 0
4
นอกจากนี้ยังไม่ จำกัด เฉพาะรายการ แต่สามารถใช้กับประเภทอื่น ๆ ได้เช่นการจับคู่ตัวสร้างค่า Just และ Nothing ของประเภทอาจจะเพื่อ "แกะ" ค่า:
Prelude> case (Just 1) of (Just x) -> succ x; Nothing -> 0
2
Prelude> case Nothing of (Just x) -> succ x; Nothing -> 0
0
แน่นอนว่าสิ่งเหล่านี้เป็นเพียงตัวอย่างของเล่นเท่านั้นและฉันไม่ได้พยายามอธิบายอย่างเป็นทางการหรือละเอียดถี่ถ้วน แต่ควรพอที่จะเข้าใจแนวคิดพื้นฐาน
คุณควรเริ่มต้นด้วยหน้า Wikipediaที่ให้คำอธิบายที่ดีทีเดียว จากนั้นอ่านบทที่เกี่ยวข้องของWikibook Haskell
นี่เป็นคำจำกัดความที่ดีจากวิกิตำราด้านบน:
ดังนั้นการจับคู่รูปแบบจึงเป็นวิธีการกำหนดชื่อให้กับสิ่งต่างๆ (หรือผูกชื่อกับสิ่งเหล่านั้น) และอาจแยกนิพจน์ออกเป็นนิพจน์ย่อยในเวลาเดียวกัน (เช่นเดียวกับที่เราทำกับรายการในนิยามของแผนที่)
นี่คือตัวอย่างสั้น ๆ ที่แสดงประโยชน์ในการจับคู่รูปแบบ:
สมมติว่าคุณต้องการจัดเรียงองค์ประกอบในรายการ:
["Venice","Paris","New York","Amsterdam"]
ถึง (ฉันเรียงลำดับ "นิวยอร์ก")
["Venice","New York","Paris","Amsterdam"]
ในภาษาที่จำเป็นกว่าที่คุณจะเขียน:
function up(city, cities){
for(var i = 0; i < cities.length; i++){
if(cities[i] === city && i > 0){
var prev = cities[i-1];
cities[i-1] = city;
cities[i] = prev;
}
}
return cities;
}
ในภาษาที่ใช้งานได้คุณควรเขียน:
let up list value =
match list with
| [] -> []
| previous::current::tail when current = value -> current::previous::tail
| current::tail -> current::(up tail value)
ดังที่คุณเห็นโซลูชันที่จับคู่รูปแบบมีสัญญาณรบกวนน้อยลงคุณจะเห็นได้ชัดเจนว่าอะไรคือกรณีที่แตกต่างกันและความสะดวกในการเดินทางและจัดโครงสร้างรายการของเรา