'Kết hợp mẫu' trong các ngôn ngữ chức năng là gì?


127

Tôi đang đọc về lập trình chức năng và tôi nhận thấy rằng Kết hợp mẫu được đề cập trong nhiều bài viết là một trong những tính năng cốt lõi của ngôn ngữ chức năng.

Ai đó có thể giải thích cho nhà phát triển Java / C ++ / JavaScript nghĩa là gì không?


Câu trả lời:


141

Hiểu khớp mẫu đòi hỏi phải giải thích ba phần:

  1. Các loại dữ liệu đại số.
  2. Khớp mẫu là gì
  3. Tại sao nó tuyệt vời.

Các loại dữ liệu đại số một cách ngắn gọn

Các ngôn ngữ chức năng giống như ML cho phép bạn xác định các loại dữ liệu đơn giản được gọi là "tách rời các hiệp hội" hoặc "các loại dữ liệu đại số". Các cấu trúc dữ liệu này là các thùng chứa đơn giản và có thể được định nghĩa đệ quy. Ví dụ:

type 'a list =
    | Nil
    | Cons of 'a * 'a list

định nghĩa một cấu trúc dữ liệu giống như ngăn xếp. Hãy nghĩ về nó tương đương với C # này:

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;
        }
    }
}

Vì vậy, các định danh ConsNilđịnh nghĩa đơn giản một lớp đơn giản, trong đó of x * y * z * ...định nghĩa một hàm tạo và một số kiểu dữ liệu. Các tham số cho hàm tạo không được đặt tên, chúng được xác định theo vị trí và loại dữ liệu.

Bạn tạo các thể hiện của a listlớp như vậy:

let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))

Điều này giống như:

Stack<int> x = new Cons(1, new Cons(2, new Cons(3, new Cons(4, new Nil()))));

Kết hợp mẫu trong một tóm tắt

Mẫu phù hợp là một loại thử nghiệm. Vì vậy, giả sử chúng ta đã tạo một đối tượng ngăn xếp như ở trên, chúng ta có thể thực hiện các phương thức để xem và bật ngăn xếp như sau:

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ác phương thức trên là tương đương (mặc dù không được triển khai như vậy) với C # sau:

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();
}

(Hầu như luôn luôn, các ngôn ngữ ML triển khai khớp mẫu mà không cần kiểm tra kiểu hoặc thời gian chạy, do đó, mã C # có phần lừa đảo. Hãy gạt chi tiết triển khai sang một bên bằng cách vẫy tay :))

Phân rã cấu trúc dữ liệu một cách ngắn gọn

Ok, chúng ta hãy quay lại với phương thức peek:

let peek s =
    match s with
    | Cons(hd, tl) -> hd
    | Nil -> failwith "Empty stack"

Bí quyết là hiểu rằng các định danh hdtlđịnh danh là các biến (errm ... vì chúng không thay đổi, chúng không thực sự là "biến", mà là "giá trị";)). Nếu scó kiểu Cons, thì chúng ta sẽ rút các giá trị của nó ra khỏi hàm tạo và liên kết chúng với các biến có tên hdtl.

Khớp mẫu rất hữu ích vì nó cho phép chúng ta phân tách cấu trúc dữ liệu theo hình dạng của nó thay vì nội dung của nó . Vì vậy, hãy tưởng tượng nếu chúng ta định nghĩa một cây nhị phân như sau:

type 'a tree =
    | Node of 'a tree * 'a * 'a tree
    | Nil

Chúng ta có thể định nghĩa một số phép quay của cây như sau:

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

(Hàm let rotateRight = functiontạo là cú pháp đường cho let rotateRight s = match s with ....)

Vì vậy, ngoài việc ràng buộc cấu trúc dữ liệu với các biến, chúng ta cũng có thể đi sâu vào nó. Hãy nói rằng chúng ta có một nút let x = Node(Nil, 1, Nil). Nếu chúng tôi gọi rotateLeft x, chúng tôi sẽ kiểm tra xmẫu đầu tiên không khớp vì mẫu đúng có loại Nilthay vìNode . Nó sẽ chuyển sang mẫu tiếp theo x -> x, sẽ khớp với bất kỳ đầu vào nào và trả lại nó không được sửa đổi.

Để so sánh, chúng tôi sẽ viết các phương thức trên trong C # là:

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))));
    }
}

Đối với nghiêm túc.

Kết hợp mẫu là tuyệt vời

Bạn có thể triển khai một cái gì đó tương tự như khớp mẫu trong C # bằng cách sử dụng mẫu khách truy cập , nhưng nó gần như không linh hoạt vì bạn không thể phân tách hiệu quả các cấu trúc dữ liệu phức tạp. Hơn nữa, nếu bạn đang sử dụng khớp mẫu, trình biên dịch sẽ cho bạn biết nếu bạn bỏ qua một trường hợp . Làm thế nào là tuyệt vời?

Hãy suy nghĩ về cách bạn triển khai chức năng tương tự trong C # hoặc ngôn ngữ mà không khớp mẫu. Hãy suy nghĩ về cách bạn làm điều đó mà không cần kiểm tra thử nghiệm và diễn xuất trong thời gian chạy. Nó chắc chắn không khó , chỉ cồng kềnh và cồng kềnh. Và bạn không có trình biên dịch kiểm tra để đảm bảo rằng bạn đã bao quát mọi trường hợp.

Vì vậy, khớp mẫu giúp bạn phân tách và điều hướng các cấu trúc dữ liệu theo một cú pháp nhỏ gọn, rất thuận tiện, nó cho phép trình biên dịch kiểm tra logic của mã của bạn, ít nhất là một chút. Nó thực sự một tính năng sát thủ.


+1 nhưng đừng quên các ngôn ngữ khác có khớp mẫu như Mathicala.
JD

1
"errm ... vì chúng không thay đổi, chúng không thực sự là" biến ", mà là" giá trị ";)" Chúng các biến; đó là giống có thể thay đổi được dán nhãn sai . Tuy nhiên, câu trả lời tuyệt vời!
Doval

3
"Hầu như luôn luôn, các ngôn ngữ ML thực hiện khớp mẫu mà không cần kiểm tra loại hoặc thời gian chạy" <- Cách thức hoạt động này? Bạn có thể chỉ cho tôi một mồi?
David Moles

1
@DavidMoles: Hệ thống loại giúp có thể bỏ qua tất cả các kiểm tra thời gian chạy bằng cách chứng minh các kết quả khớp mẫu là toàn diện và không dư thừa. Nếu bạn cố gắng cung cấp một ngôn ngữ như SML, OCaml hoặc F # một mẫu phù hợp không đầy đủ hoặc chứa dư thừa thì trình biên dịch sẽ cảnh báo bạn trong thời gian biên dịch. Đây là một tính năng cực kỳ mạnh mẽ vì nó cho phép bạn loại bỏ kiểm tra thời gian chạy bằng cách sắp xếp lại mã của bạn, tức là bạn có thể có các khía cạnh của mã được chứng minh là đúng. Hơn nữa, nó là dễ hiểu!
JD

@JonHarrop Tôi có thể thấy nó sẽ hoạt động như thế nào (hiệu quả tương tự như gửi tin nhắn động) nhưng tôi không thể thấy cách bạn chọn một chi nhánh trong thời gian chạy mà không cần kiểm tra loại.
David Moles

33

Câu trả lời ngắn: Kết hợp mẫu phát sinh do các ngôn ngữ chức năng coi dấu bằng là một khẳng định tương đương thay vì gán.

Câu trả lời dài: Khớp mẫu là một hình thức gửi đi dựa trên hình dạng của hình ảnh về giá trị mà nó đưa ra. Trong một ngôn ngữ chức năng, các kiểu dữ liệu mà bạn xác định thường là những gì được gọi là các hiệp hội phân biệt đối xử hoặc các kiểu dữ liệu đại số. Ví dụ, danh sách (được liên kết) là gì? Một danh sách được liên kết Listcủa một số loại alà danh sách trống Nilhoặc một số phần tử của loại a Consed vào một List a(danh sách as). Trong Haskell (ngôn ngữ chức năng mà tôi quen thuộc nhất), chúng tôi viết này

data List a = Nil
            | Cons a (List a)

Tất cả các hiệp hội phân biệt được định nghĩa theo cách này: một loại duy nhất có một số cách cố định khác nhau để tạo ra nó; những người sáng tạo, như NilConsở đây, được gọi là các nhà xây dựng. Điều này có nghĩa là một giá trị của loại List acó thể đã được tạo bằng hai hàm tạo khác nhau, nó có thể có hai hình dạng khác nhau. Vì vậy, giả sử chúng ta muốn viết một headhàm để lấy phần tử đầu tiên của danh sách. Trong Haskell, chúng tôi sẽ viết điều này như là

-- `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 acác giá trị có thể có hai loại khác nhau, chúng ta cần xử lý riêng từng loại; đây là mô hình phù hợp. Trong head x, nếu xkhớp với mẫu Nil, thì chúng ta chạy trường hợp đầu tiên; nếu nó phù hợp với mô hình Cons h _, chúng tôi chạy thứ hai.

Câu trả lời ngắn gọn, giải thích: Tôi nghĩ một trong những cách tốt nhất để suy nghĩ về hành vi này là bằng cách thay đổi cách bạn nghĩ về dấu bằng. Trong các ngôn ngữ ngoặc vuông, nói chung, =biểu thị sự phân công: a = bcó nghĩa là tạo ara b. Tuy nhiên, trong rất nhiều ngôn ngữ chức năng =biểu thị một khẳng định về sự bình đẳng: let Cons a (Cons b Nil) = frob x khẳng định rằng thứ bên trái, Cons a (Cons b Nil)tương đương với thứ bên phải , frob x; Ngoài ra, tất cả các biến được sử dụng ở bên trái trở nên hữu hình. Đây cũng là điều đang xảy ra với các đối số chức năng: chúng tôi khẳng định rằng đối số đầu tiên trông như thế nào Nilvà nếu không, chúng tôi tiếp tục kiểm tra.


Thật là một cách thú vị để suy nghĩ về dấu bằng. Cảm ơn đã chia sẻ điều đó!
jrahhali

2
Consnghĩa là gì?
Roymunson

2
@Roymunson: Conskhuyết điểm tructor rằng xây dựng một danh sách (liên kết) ra khỏi một đầu (các a) và một cái đuôi (các List a). Tên đến từ Lisp. Trong Haskell, đối với loại danh sách tích hợp, đó là :toán tử (vẫn được phát âm là "khuyết điểm").
Antal Spector-Zabusky

23

Nó có nghĩa là thay vì viết

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;
}

Bạn có thể viết

f(0, 0) = NaN;
f(x, 0) | x > 0 = Infinity;
        | else  = -Infinity;
f(x, y) = (double)x / y;

Hey, C ++ cũng hỗ trợ khớp mẫu.

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;
};

1
Trong Scala: nhập Double._ def split = {value: (Double, Double) => value khớp {case (0,0) => NaN case (x, 0) => if (x> 0) positiveInfinity khác NegativeInfinity case (x, y) => x / y}}
fracca

12

Kết hợp mẫu giống như các phương thức quá tải trên steroid. Trường hợp đơn giản nhất sẽ giống như những gì bạn thấy trong java, các đối số là một danh sách các loại có tên. Phương thức đúng để gọi dựa trên các đối số được truyền vào và nó nhân đôi khi gán các đối số đó cho tên tham số.

Các mẫu chỉ cần tiến thêm một bước và có thể phá hủy các đối số được truyền thêm. Nó cũng có khả năng sử dụng các vệ sĩ để thực sự khớp dựa trên giá trị của đối số. Để chứng minh, tôi sẽ giả vờ như JavaScript có khớp mẫu.

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

Trong foo2, nó dự kiến ​​là một mảng, nó phá vỡ đối số thứ hai, mong đợi một đối tượng có hai đạo cụ (prop1, prop2) và gán các giá trị của các thuộc tính đó cho các biến d và e, và sau đó hy vọng đối số thứ ba sẽ là 35.

Không giống như trong JavaScript, các ngôn ngữ có khớp mẫu thường cho phép nhiều hàm có cùng tên, nhưng các mẫu khác nhau. Theo cách này, nó giống như quá tải phương pháp. Tôi sẽ đưa ra một ví dụ trong erlang:

fibo(0) -> 0 ;
fibo(1) -> 1 ;
fibo(N) when N > 0 -> fibo(N-1) + fibo(N-2) .

Làm mờ đôi mắt của bạn một chút và bạn có thể tưởng tượng điều này trong javascript. Một cái gì đó như thế này có thể:

function fibo(0){return 0;}
function fibo(1){return 1;}
function fibo(N) when N > 0 {return fibo(N-1) + fibo(N-2);}

Điểm chính là khi bạn gọi fibo, việc triển khai nó sử dụng dựa trên các đối số, nhưng trong đó Java bị giới hạn ở các kiểu như là phương tiện quá tải duy nhất, khớp mẫu có thể làm được nhiều hơn.

Ngoài việc quá tải chức năng như được hiển thị ở đây, nguyên tắc tương tự có thể được áp dụng ở những nơi khác, chẳng hạn như các tuyên bố trường hợp hoặc các giả định phá hủy. JavaScript thậm chí có cái này trong 1.7 .


8

Khớp mẫu cho phép bạn khớp một giá trị (hoặc một đối tượng) với một số mẫu để chọn một nhánh của mã. Từ quan điểm của C ++, nó có thể nghe hơi giống với switchtuyên bố. Trong các ngôn ngữ chức năng, khớp mẫu có thể được sử dụng để khớp trên các giá trị nguyên thủy tiêu chuẩn, chẳng hạn như số nguyên. Tuy nhiên, nó hữu ích hơn cho các loại sáng tác.

Trước tiên, hãy chứng minh khớp mẫu trên các giá trị nguyên thủy (sử dụng giả mở rộng 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')
}

Việc sử dụng thứ hai liên quan đến các loại dữ liệu chức năng như bộ dữ liệu (cho phép bạn lưu trữ nhiều đối tượng trong một giá trị) và các hiệp hội phân biệt đối xử cho phép bạn tạo một loại có thể chứa một trong một số tùy chọn. Điều này nghe có vẻ giống như enumngoại trừ rằng mỗi nhãn cũng có thể mang một số giá trị. Trong cú pháp giả C ++:

enum Shape { 
  Rectangle of { int left, int top, int width, int height }
  Circle of { int x, int y, int radius }
}

Một giá trị của loại Shapebây giờ có thể chứa hoặc Rectanglevới tất cả các tọa độ hoặc a Circlevới tâm và bán kính. Khớp mẫu cho phép bạn viết một hàm để làm việc với Shapekiểu:

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)
}

Cuối cùng, bạn cũng có thể sử dụng các mẫu lồng nhau kết hợp cả hai tính năng. Ví dụ: bạn có thể sử dụng Circle(0, 0, radius)để khớp với tất cả các hình có tâm ở điểm [0, 0] và có bất kỳ bán kính nào (giá trị của bán kính sẽ được gán cho biến mớiradius ).

Điều này nghe có vẻ hơi lạ lẫm từ quan điểm của C ++, nhưng tôi hy vọng rằng giả danh C ++ của tôi làm cho lời giải thích rõ ràng. Lập trình hàm dựa trên các khái niệm khá khác nhau, vì vậy nó có ý nghĩa tốt hơn trong một ngôn ngữ chức năng!


5

Khớp mẫu là nơi trình thông dịch cho ngôn ngữ của bạn sẽ chọn một chức năng cụ thể dựa trên cấu trúc và nội dung của các đối số bạn đưa ra.

Nó không chỉ là một tính năng ngôn ngữ chức năng mà còn có sẵn cho nhiều ngôn ngữ khác nhau.

Lần đầu tiên tôi bắt gặp ý tưởng là khi tôi học prolog, nơi nó thực sự là trung tâm của ngôn ngữ.

ví dụ

cuối cùng ([LastItem], LastItem).

cuối cùng ([Đầu | Đuôi], LastItem): - cuối cùng (Đuôi, LastItem).

Đoạn mã trên sẽ đưa ra mục cuối cùng của danh sách. Các đối số đầu vào là đầu tiên và kết quả là thứ hai.

Nếu chỉ có một mục trong danh sách, trình thông dịch sẽ chọn phiên bản đầu tiên và đối số thứ hai sẽ được đặt bằng với mục đầu tiên, tức là một giá trị sẽ được gán cho kết quả.

Nếu danh sách có cả đầu và đuôi, thông dịch viên sẽ chọn phiên bản thứ hai và lặp lại cho đến khi chỉ còn một mục trong danh sách.


Ngoài ra, như bạn có thể thấy từ ví dụ, trình thông dịch cũng có thể tự động chia một đối số thành nhiều biến số (ví dụ: [Head | Tail])
charlieb

4

Đối với nhiều người, chọn một khái niệm mới sẽ dễ dàng hơn nếu một số ví dụ dễ dàng được cung cấp, vì vậy chúng tôi đi đây:

Giả sử bạn có một danh sách gồm ba số nguyên và muốn thêm phần tử thứ nhất và thứ ba. Nếu không khớp mẫu, bạn có thể làm như thế này (ví dụ trong Haskell):

Prelude> let is = [1,2,3]
Prelude> head is + is !! 2
4

Bây giờ, mặc dù đây là một ví dụ về đồ chơi, hãy tưởng tượng chúng ta muốn liên kết số nguyên thứ nhất và thứ ba với các biến và tính tổng chúng:

addFirstAndThird is =
    let first = head is
        third = is !! 3
    in first + third

Việc trích xuất các giá trị từ một cấu trúc dữ liệu là những gì khớp mẫu. Về cơ bản, bạn "phản chiếu" cấu trúc của một cái gì đó, đưa ra các biến để ràng buộc cho các địa điểm ưa thích:

addFirstAndThird [first,_,third] = first + third

Khi bạn gọi hàm này với [1,2,3] làm đối số của nó, [1,2,3] sẽ được hợp nhất với [đầu tiên _, thứ ba], ràng buộc đầu tiên với 1, thứ ba đến 3 và loại bỏ 2 ( _là giữ chỗ cho những điều bạn không quan tâm).

Bây giờ, nếu bạn chỉ muốn khớp danh sách với 2 là phần tử thứ hai, bạn có thể làm như thế này:

addFirstAndThird [first,2,third] = first + third

Điều này sẽ chỉ hoạt động cho các danh sách có 2 là phần tử thứ hai của chúng và đưa ra một ngoại lệ khác, bởi vì không có định nghĩa nào cho addFirstAndThird được đưa ra cho các danh sách không khớp.

Cho đến bây giờ, chúng tôi chỉ sử dụng khớp mẫu để phá hủy liên kết. Ở trên đó, bạn có thể đưa ra nhiều định nghĩa của cùng một chức năng, trong đó định nghĩa khớp đầu tiên được sử dụng, do đó, khớp mẫu giống như "câu lệnh chuyển đổi trên âm thanh nổi":

addFirstAndThird [first,2,third] = first + third
addFirstAndThird _ = 0

addFirstAndThird sẽ vui vẻ thêm phần tử thứ nhất và thứ ba của danh sách với 2 làm phần tử thứ hai của chúng và nếu không thì "rơi qua" và "trả lại" 0. Chức năng "giống như chuyển đổi" này không thể chỉ được sử dụng trong các định nghĩa hàm, ví dụ:

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

Hơn nữa, nó không bị hạn chế trong danh sách, nhưng cũng có thể được sử dụng với các loại khác, ví dụ, khớp với các hàm tạo giá trị Chỉ và Không có gì của loại Có thể để "mở khóa" giá trị:

Prelude> case (Just 1) of (Just x) -> succ x; Nothing -> 0
2
Prelude> case Nothing of (Just x) -> succ x; Nothing -> 0
0

Chắc chắn, đó chỉ là những ví dụ về đồ chơi, và tôi thậm chí không cố gắng đưa ra một lời giải thích chính thức hay đầy đủ, nhưng họ nên đủ để nắm bắt khái niệm cơ bản.


3

Bạn nên bắt đầu với trang Wikipedia đưa ra một lời giải thích khá tốt. Sau đó, đọc chương liên quan của wikibook Haskell .

Đây là một định nghĩa hay từ wikibook ở trên:

Vì vậy, khớp mẫu là một cách gán tên cho các vật (hoặc ràng buộc các tên đó với các thứ đó) và có thể chia các biểu thức thành các biểu thức con cùng một lúc (như chúng ta đã làm với danh sách trong định nghĩa của bản đồ).


3
Lần tới tôi sẽ đề cập đến câu hỏi rằng tôi đã đọc wikipedia và nó đưa ra lời giải thích rất tệ.
La Mã

2

Đây là một ví dụ thực sự ngắn cho thấy tính hữu dụng phù hợp với mẫu:

Giả sử bạn muốn sắp xếp một thành phần trong danh sách:

["Venice","Paris","New York","Amsterdam"] 

đến (Tôi đã sắp xếp "New York")

["Venice","New York","Paris","Amsterdam"] 

trong một ngôn ngữ cấp bách hơn bạn sẽ viết:

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;
}

Trong một ngôn ngữ chức năng, thay vào đó bạn sẽ viết:

let up list value =  
    match list with
        | [] -> []
        | previous::current::tail when current = value ->  current::previous::tail
        | current::tail -> current::(up tail value)

Như bạn có thể thấy giải pháp khớp mẫu có ít nhiễu hơn, bạn có thể thấy rõ các trường hợp khác nhau là gì và mức độ dễ dàng để di chuyển và hủy cấu trúc danh sách của chúng tôi.

Tôi đã viết một bài đăng blog chi tiết hơn về nó ở đây .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.