Câu hỏi Mathematica: lập trình biểu tượng là gì?


Tôi là một fan hâm mộ lớn của Stephen Wolfram, nhưng anh ấy chắc chắn là một người không nhút nhát của tooting sừng của riêng mình. Trong nhiều tài liệu tham khảo, ông mở rộng Mathematica như một mô hình lập trình biểu tượng khác. Tôi không phải là người dùng Mathematica.

Câu hỏi của tôi là: lập trình biểu tượng này là gì? Và nó so sánh với các ngôn ngữ chức năng (như Haskell) như thế nào?


76
2017-12-13 16:28


gốc




Các câu trả lời:


Bạn có thể nghĩ về lập trình biểu tượng của Mathematica như một hệ thống tìm kiếm và thay thế nơi bạn lập trình bằng cách chỉ định các quy tắc tìm kiếm và thay thế.

Ví dụ: bạn có thể chỉ định quy tắc sau

area := Pi*radius^2;

Lần tới bạn sử dụng area, nó sẽ được thay thế bằng Pi*radius^2. Bây giờ, giả sử bạn xác định quy tắc mới

radius:=5

Bây giờ, bất cứ khi nào bạn sử dụng radius, nó sẽ được viết lại thành 5. Nếu bạn đánh giá area nó sẽ được viết lại thành Pi*radius^2 kích hoạt quy tắc viết lại cho radius và bạn sẽ nhận được Pi*5^2 như một kết quả trung gian. Biểu mẫu mới này sẽ kích hoạt quy tắc viết lại được tích hợp sẵn cho ^ hoạt động để biểu thức sẽ được viết lại thành Pi*25. Tại thời điểm này viết lại dừng lại vì không có quy tắc áp dụng.

Bạn có thể mô phỏng lập trình hàm bằng cách sử dụng các quy tắc thay thế của bạn làm hàm. Ví dụ, nếu bạn muốn định nghĩa một hàm bổ sung, bạn có thể làm

add[a_,b_]:=a+b

Hiện nay add[x,y] được viết lại thành x+y. Nếu bạn muốn thêm vào chỉ áp dụng cho số a, b, bạn có thể thay thế

add[a_?NumericQ, b_?NumericQ] := a + b

Hiện nay, add[2,3] được viết lại thành 2+3 sử dụng quy tắc của bạn và sau đó vào 5 sử dụng quy tắc tích hợp cho +, trong khi add[test1,test2] vẫn không thay đổi.

Dưới đây là ví dụ về quy tắc thay thế tương tác

a := ChoiceDialog["Pick one", {1, 2, 3, 4}]
a+1

Đây, a được thay thế bằng ChoiceDialog, sau đó được thay thế bằng số người dùng đã chọn trên hộp thoại bật lên, điều này làm cho cả số lượng số và kích hoạt quy tắc thay thế cho +. Đây, ChoiceDialog như một quy tắc thay thế tích hợp dọc theo dòng "thay thế ChoiceDialog [một số nội dung] với giá trị của nút mà người dùng đã nhấp vào".

Các quy tắc có thể được xác định bằng các điều kiện mà bản thân chúng cần phải trải qua quá trình viết lại quy tắc để tạo ra True hoặc là False. Ví dụ giả sử bạn đã phát minh ra một phương pháp giải phương trình mới, nhưng bạn nghĩ nó chỉ hoạt động khi kết quả cuối cùng của phương pháp của bạn là dương. Bạn có thể thực hiện quy tắc sau

 solve[x + 5 == b_] := (result = b - 5; result /; result > 0)

Đây, solve[x+5==20] được thay thế bằng 15, nhưng solve[x + 5 == -20] không thay đổi vì không có quy tắc nào áp dụng. Điều kiện ngăn quy tắc này áp dụng là /;result>0. Đánh giá cơ bản trông tiềm năng đầu ra của ứng dụng quy tắc để quyết định xem có nên tiếp tục với nó hay không.

Đánh giá của Mathematica tham lam viết lại mọi mẫu với một trong các quy tắc áp dụng cho biểu tượng đó. Đôi khi bạn muốn có quyền kiểm soát tốt hơn và trong trường hợp đó, bạn có thể xác định quy tắc của riêng mình và áp dụng chúng theo cách thủ công như thế này

myrules={area->Pi radius^2,radius->5}
area//.myrules

Điều này sẽ áp dụng các quy tắc được xác định trong myrules cho đến khi kết quả ngừng thay đổi. Điều này khá giống với bộ đánh giá mặc định, nhưng bây giờ bạn có thể có nhiều bộ quy tắc và áp dụng chúng một cách có chọn lọc. Nâng cao hơn thí dụ cho thấy cách tạo một bộ đánh giá giống như Prolog để tìm kiếm các chuỗi các ứng dụng quy tắc.

Một nhược điểm của phiên bản Mathematica hiện tại xuất hiện khi bạn cần sử dụng bộ đánh giá mặc định của Mathematica (để sử dụng Integrate, Solve, v.v.)  muốn thay đổi chuỗi đánh giá mặc định. Điều đó là có thể nhưng phức tạpvà tôi muốn nghĩ rằng một số triển khai trong tương lai của lập trình biểu tượng sẽ có một cách thanh lịch hơn để kiểm soát chuỗi đánh giá


70
2017-12-13 22:12



@ Yaro Tôi đoán điều này trở nên thú vị hơn khi bạn nhận được các quy tắc như là kết quả của các hàm (như trong Giải quyết, DSolve, v.v.) - Dr. belisarius
Nhưng bạn có thể nghĩ Solve chỉ là một bộ quy tắc viết lại khác. Khi bạn đưa ra một số phương trình mà Mathematica không thể giải quyết Solve[hard_equations] vẫn như Solve[hard_equations] và bạn có thể xác định một tùy chỉnh Solve quy tắc áp dụng trong trường hợp này. Trong trường hợp này, tôi đoán họ sử dụng /; có điều kiện để xác định mẫu cho "bất kỳ phương trình nào có thể được giải quyết bằng các phương thức trong Mathematica", do đó, đối với các quy tắc tích hợp cứng không áp dụng và Solve vẫn ở dạng ban đầu - Yaroslav Bulatov
Tôi nghĩ điều đó quá phức tạp, các chương trình Mathematica về cơ bản chỉ là những quy tắc thay thế. Thi hành là một quá trình áp dụng các quy tắc hiện hành để nhập vào cho đến khi không có quy tắc nào phù hợp - Yaroslav Bulatov
1 Lời giải thích no-horn-thổi không được giải thích rất tốt. Có lẽ điều duy nhất tôi muốn thêm là có nhiều gazillions của các quy tắc và thuật toán đã được bao gồm trong hạt nhân đại diện cho gần như tất cả các thư viện toán học có sẵn trong hầu hết các ngôn ngữ, và sau đó một số chi tiết. - Dr. belisarius
Simon, tính toán lambda chính nó chỉ là một trong những hệ thống viết lại. Viết lại thuật ngữ là cách tiếp cận tổng quát hơn bất kỳ TRS cụ thể nào. - SK-logic


Khi tôi nghe cụm từ "lập trình biểu tượng", LISP, Prolog và (có) Mathematica ngay lập tức nhảy vào tâm trí. Tôi sẽ mô tả một môi trường lập trình biểu tượng như một trong đó các biểu thức được sử dụng để biểu diễn văn bản chương trình cũng là cấu trúc dữ liệu chính. Kết quả là, nó trở nên rất dễ dàng để xây dựng abstractions khi trừu tượng vì dữ liệu có thể dễ dàng được chuyển đổi thành mã và ngược lại.

Mathematica khai thác rất nhiều khả năng này. Thậm chí còn nặng nề hơn LISP và Prolog (IMHO).

Như một ví dụ về lập trình biểu tượng, hãy xem xét chuỗi sự kiện sau đây. Tôi có tệp CSV trông như sau:

r,1,2
g,3,4

Tôi đọc tệp đó trong:

Import["somefile.csv"]
--> {{r,1,2},{g,3,4}}

Dữ liệu kết quả hay mã? Cả hai. Nó là dữ liệu kết quả từ việc đọc tệp, nhưng nó cũng sẽ xảy ra là biểu thức sẽ xây dựng dữ liệu đó. Tuy nhiên, khi mã đi, biểu thức này là trơ do kết quả của việc đánh giá nó đơn giản là chính nó.

Vì vậy, bây giờ tôi áp dụng một chuyển đổi cho kết quả:

% /. {c_, x_, y_} :> {c, Disk[{x, y}]}
--> {{r,Disk[{1,2}]},{g,Disk[{3,4}]}}

Nếu không có chi tiết, tất cả những gì đã xảy ra là Disk[{...}] đã được bao bọc xung quanh hai số cuối cùng từ mỗi dòng đầu vào. Kết quả vẫn là dữ liệu / mã, nhưng vẫn trơ. Một biến đổi khác:

% /. {"r" -> Red, "g" -> Green}
--> {{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}

Có, vẫn còn trơ. Tuy nhiên, bởi một sự trùng hợp đáng chú ý kết quả cuối cùng này chỉ xảy ra là một danh sách các chỉ thị hợp lệ trong ngôn ngữ được xây dựng trong ngôn ngữ cụ thể của Mathematica cho đồ họa. Một biến đổi cuối cùng, và mọi thứ bắt đầu xảy ra:

% /. x_ :> Graphics[x]
--> Graphics[{{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}]

Trên thực tế, bạn sẽ không thấy kết quả cuối cùng. Trong một màn trình diễn hoành tráng của đường cú pháp, Mathematica sẽ hiển thị hình ảnh của các vòng tròn màu đỏ và màu xanh lá cây:

alt text

Nhưng niềm vui không dừng lại ở đó. Bên dưới tất cả các đường cú pháp, chúng ta vẫn có biểu hiện biểu tượng. Tôi có thể áp dụng một quy tắc chuyển đổi khác:

% /. Red -> Black

alt text

Mau! Vòng tròn màu đỏ trở thành màu đen.

Đây là loại "biểu tượng đẩy" đặc trưng cho lập trình biểu tượng. Phần lớn các lập trình Mathematica có tính chất này.

Chức năng so với biểu tượng

Tôi sẽ không giải quyết sự khác biệt giữa lập trình biểu tượng chức năng và chi tiết, nhưng tôi sẽ đóng góp một vài nhận xét.

Người ta có thể xem lập trình biểu tượng như một câu trả lời cho câu hỏi: "Điều gì sẽ xảy ra nếu tôi cố gắng mô hình hóa mọi thứ chỉ bằng cách sử dụng các phép biến đổi biểu thức?" Ngược lại, lập trình chức năng có thể được xem như một câu trả lời cho: "Điều gì sẽ xảy ra nếu tôi cố gắng mô hình hóa mọi thứ chỉ bằng các hàm?" Cũng giống như lập trình biểu tượng, lập trình chức năng giúp dễ dàng xây dựng nhanh các lớp trừu tượng hóa. Ví dụ tôi đưa ra ở đây có thể dễ dàng được tái tạo, ví dụ, Haskell sử dụng phương pháp hoạt hình phản ứng chức năng. Lập trình hàm là tất cả về thành phần hàm, hàm mức cao hơn, bộ phối hợp - tất cả những thứ tiện lợi mà bạn có thể làm với các hàm.

Mathematica được tối ưu hóa rõ ràng cho lập trình biểu tượng. Có thể viết mã theo phong cách chức năng, nhưng các tính năng chức năng trong Mathematica thực sự chỉ là một veneer mỏng trên các phép biến đổi (và một trừu tượng bị rò rỉ ở đó, xem chú thích dưới đây).

Haskell được tối ưu hóa rõ ràng cho lập trình hàm. Có thể viết mã theo kiểu tượng trưng, ​​nhưng tôi sẽ giải thích rằng cú pháp đại diện của các chương trình và dữ liệu khá khác biệt, làm cho kinh nghiệm tối ưu.

Kết luận

Tóm lại, tôi ủng hộ rằng có một sự phân biệt giữa lập trình chức năng (như được hình thành bởi Haskell) và lập trình biểu tượng (như đã được Epitomized bởi Mathematica). Tôi nghĩ rằng nếu một người nghiên cứu cả hai, thì người ta sẽ học nhiều hơn nhiều so với việc học chỉ là một - thử thách tối thượng về sự khác biệt.


Leaky chức năng trừu tượng trong Mathematica?

Yup, bị rò rỉ. Hãy thử điều này, ví dụ:

f[x_] := g[Function[a, x]];
g[fn_] := Module[{h}, h[a_] := fn[a]; h[0]];
f[999]

Được báo cáo hợp lệ, và được thừa nhận bởi, WRI. Phản hồi: tránh sử dụng Function[var, body] (Function[body] được rồi).


72
2017-12-14 03:54



WRI đã thực sự khuyên bạn nên tránh Function[var, body]? Điều đó thật lạ vì nó được đề xuất trong tài liệu ... - Simon
@ Simon: Vâng, tôi có một email từ WRI tuyên bố rằng nếu tôi lo lắng về ngữ nghĩa của một chức năng thay đổi dựa trên việc bất kỳ người gọi nào trong "chuỗi cuộc gọi" sẽ sử dụng biểu tượng giống như tên, thì tôi nên tránh sử dụng Function[var, body]. Không có lời giải thích nào được đưa ra về lý do tại sao điều này không thể sửa được, nhưng tôi suy đoán rằng Function đã được khoảng từ 1.0 nó sẽ là thảm họa để thay đổi hành vi của nó vào cuối trò chơi này. Vấn đề được mô tả trong (hơi) chi tiết hơn đây. - WReach
Với mức độ tiếp xúc của ruột của nó trong mma, tôi thậm chí không chắc chắn rằng Function có thể được chữa khỏi, thậm chí về nguyên tắc - ít nhất với các ngữ nghĩa xâm nhập hiện tại của Rule và RuleDelayed, không tôn trọng các ràng buộc xây dựng phạm vi bên trong, bao gồm cả bản thân. Hiện tượng này dường như với tôi liên quan nhiều hơn đến tài sản này của Rule và RuleDelayed, đặc biệt là Function. Nhưng dù bằng cách nào, tôi đồng ý rằng việc thay đổi điều này rất nguy hiểm ngay bây giờ. Tệ quá, bởi vì Function[var,body] không nên được sử dụng - các lỗi như vậy sẽ gần như không thể bắt được trong các dự án khá lớn. - Leonid Shifrin
@WReach Toán học, đó là một bản sao FOSS Mathematica không có vấn đề chức năng bị rò rỉ! Tôi vừa thử nó. - M. Alaggan


Như những người khác ở đây đã được đề cập, Mathematica làm rất nhiều thuật ngữ viết lại. Có lẽ Haskell không phải là so sánh tốt nhất, nhưng Nguyên chất là một ngôn ngữ viết lại thuật ngữ có chức năng tốt đẹp (nên cảm thấy quen thuộc với những người có nền Haskell). Có thể đọc trang Wiki của họ trên thuật ngữ viết lại sẽ làm sáng tỏ một vài điều cho bạn:

http://code.google.com/p/pure-lang/wiki/Rewriting


9
2017-12-14 09:37



Về mặt ngữ nghĩa, Pure là giống như Scheme (gõ năng động, mặc định để đánh giá háo hức) hoặc OCaml (có các ô tham chiếu rõ ràng). Nhưng cú pháp và thư viện làm gương của Haskell. - dubiousjim


Mathematica đang sử dụng thuật ngữ viết lại rất nhiều. Ngôn ngữ cung cấp cú pháp đặc biệt cho các dạng viết lại khác nhau, hỗ trợ đặc biệt cho các quy tắc và chiến lược. Các mô hình không phải là "mới" và tất nhiên nó không phải là duy nhất, nhưng họ chắc chắn trên một cạnh chảy máu của điều này "lập trình biểu tượng" điều, cùng với các cầu thủ mạnh khác như Axiom.

Đối với so sánh với Haskell, tốt, bạn có thể viết lại ở đó, với một chút giúp đỡ từ phế liệu thư viện boilerplate của bạn, nhưng nó gần như không dễ dàng như trong một Mathematica động được gõ.


5
2017-12-13 18:23