Hiệu quả của lập trình chức năng thuần túy


397

Có ai biết sự chậm chạp tiệm cận tồi tệ nhất có thể xảy ra khi lập trình hoàn toàn có chức năng trái ngược với mệnh lệnh (nghĩa là cho phép tác dụng phụ) không?

Làm rõ từ nhận xét của itowlson : có bất kỳ vấn đề nào mà thuật toán không phá hủy được biết đến nhiều nhất là tồi tệ hơn so với thuật toán phá hủy được biết đến nhiều nhất, và nếu có thì bao nhiêu?


6
Giống như khi lập trình bắt buộc, bất kể đó là gì.
R. Martinho Fernandes

3
@jldupont: Để trả về kết quả tính toán của khóa học. Nhiều chương trình tác dụng phụ miễn phí tồn tại. Họ không thể làm gì khác hơn là tính toán đầu vào của họ. Nhưng điều đó vẫn hữu ích.
jalf

24
Tôi có thể làm cho nó tệ như bạn muốn, bằng cách viết mã chức năng của tôi rất tệ! * cười toe toét ?
itowlson

2
bạn có thể đưa ra một ví dụ về loại chậm mà bạn quan tâm. Câu hỏi của bạn hơi mơ hồ.
Peter Recore

5
Một người dùng đã xóa câu trả lời của anh ta, nhưng anh ta tuyên bố rằng phiên bản chức năng của vấn đề 8 nữ hoàng đã chạy trong hơn một phút với n = 13. Anh ta thừa nhận nó không "được viết rất tốt", vì vậy tôi đã quyết định viết phiên bản của riêng mình 8 nữ hoàng trong F #: pastebin.com/ffa8d4c4 . Không cần phải nói, chương trình chức năng thuần túy của tôi tính n = 20 chỉ trong hơn một giây.
Juliet

Câu trả lời:


531

Theo Pippenger [1996] , khi so sánh một hệ thống Lisp hoàn toàn có chức năng (và có ngữ nghĩa đánh giá nghiêm ngặt, không lười biếng) với một hệ thống có thể làm thay đổi dữ liệu, thuật toán được viết cho Lisp không tinh khiết chạy trong O ( n ) có thể được dịch với một thuật toán trong Lisp thuần chạy trong thời gian O ( n log n ) (dựa trên công việc của Ben-Amram và Galil [1992] về việc mô phỏng bộ nhớ truy cập ngẫu nhiên chỉ sử dụng con trỏ). Pippenger cũng thiết lập rằng có những thuật toán mà đó là điều tốt nhất bạn có thể làm; có những vấn đề là O ( n ) trong hệ thống không tinh khiết là ( n log n ) trong hệ thống thuần túy.

Có một vài cảnh báo được thực hiện về bài báo này. Điều quan trọng nhất là nó không giải quyết các ngôn ngữ chức năng lười biếng, chẳng hạn như Haskell. Bird, Jones và De Moor [1997] chứng minh rằng vấn đề do Pippenger xây dựng có thể được giải quyết bằng ngôn ngữ chức năng lười biếng trong thời gian O ( n ), nhưng họ không thiết lập (và theo như tôi biết, không ai có) dù có hay không không phải là một ngôn ngữ chức năng lười biếng có thể giải quyết tất cả các vấn đề trong cùng một thời gian chạy tiệm cận như một ngôn ngữ có đột biến.

Vấn đề được Pippenger xây dựng đòi hỏi Ω ( n log n ) được xây dựng cụ thể để đạt được kết quả này và không nhất thiết phải đại diện cho các vấn đề thực tế, thực tế. Có một vài hạn chế về vấn đề hơi bất ngờ, nhưng cần thiết để bằng chứng hoạt động; đặc biệt, vấn đề yêu cầu các kết quả được tính toán trực tuyến, mà không thể truy cập đầu vào trong tương lai và đầu vào bao gồm một chuỗi các nguyên tử từ một tập hợp các nguyên tử có thể, không phải là một tập hợp kích thước cố định. Và bài báo chỉ thiết lập kết quả (giới hạn dưới) cho một thuật toán không tinh khiết về thời gian chạy tuyến tính; đối với các sự cố đòi hỏi thời gian chạy lớn hơn, có thể là thêm O (log n) yếu tố được thấy trong bài toán tuyến tính có thể có thể được "hấp thụ" trong quá trình hoạt động thêm cần thiết cho các thuật toán có thời gian chạy lớn hơn. Những làm rõ và câu hỏi mở này được Ben-Amram [1996] khám phá ngắn gọn .

Trong thực tế, nhiều thuật toán có thể được thực hiện bằng một ngôn ngữ chức năng thuần túy với hiệu quả tương tự như trong một ngôn ngữ có cấu trúc dữ liệu có thể thay đổi. Để tham khảo tốt về các kỹ thuật sử dụng để thực hiện các cấu trúc dữ liệu chức năng thuần túy một cách hiệu quả, hãy xem "Cấu trúc dữ liệu chức năng thuần túy" của Chris Okasaki [Okasaki 1998] (đây là phiên bản mở rộng của luận án [Okasaki 1996] ).

Bất cứ ai cần thực hiện các thuật toán trên các cấu trúc dữ liệu thuần túy chức năng nên đọc Okasaki. Bạn luôn có thể làm chậm tốc độ O (log n ) trên mỗi thao tác bằng cách mô phỏng bộ nhớ có thể thay đổi với cây nhị phân cân bằng, nhưng trong nhiều trường hợp bạn có thể làm tốt hơn đáng kể và Okasaki mô tả nhiều kỹ thuật hữu ích, từ kỹ thuật khấu hao đến thực tế thời gian thực hiện công việc khấu hao tăng dần. Các cấu trúc dữ liệu chức năng thuần túy có thể hơi khó khăn để làm việc và phân tích, nhưng chúng cung cấp nhiều lợi ích như tính minh bạch tham chiếu hữu ích trong tối ưu hóa trình biên dịch, song song và tính toán phân tán và thực hiện các tính năng như phiên bản, hoàn tác và khôi phục.

Cũng lưu ý rằng tất cả những điều này chỉ thảo luận về thời gian chạy không có triệu chứng. Nhiều kỹ thuật để thực hiện các cấu trúc dữ liệu chức năng thuần túy cung cấp cho bạn một số yếu tố chậm liên tục nhất định, do cần có thêm sổ sách kế toán để chúng hoạt động và chi tiết triển khai ngôn ngữ được đề cập. Lợi ích của các cấu trúc dữ liệu chức năng thuần túy có thể vượt xa các yếu tố chậm chạp liên tục này, do đó, bạn thường sẽ cần phải đánh đổi dựa trên vấn đề đang bàn đến.

Người giới thiệu


50
Pippinger là cơ quan không thể tranh cãi về câu hỏi này. Nhưng chúng ta nên nhấn mạnh rằng kết quả của ông là lý thuyết , không thực tế. Khi nói đến việc làm cho cấu trúc dữ liệu chức năng trở nên thiết thực và hiệu quả, bạn không thể làm tốt hơn Okasaki.
Norman Ramsey

6
itowlson: Tôi phải thừa nhận rằng tôi đã không đọc đủ Pippenger để trả lời câu hỏi của bạn; nó đã được xuất bản trong một tạp chí đánh giá ngang hàng, được trích dẫn bởi Okasaki, và tôi đã đọc đủ để xác định rằng những tuyên bố của ông có liên quan đến câu hỏi này, nhưng không đủ để hiểu bằng chứng. Điểm nổi bật ngay lập tức mà tôi nhận được đối với các hậu quả trong thế giới thực là việc chuyển đổi thuật toán không tinh khiết O ( n ) thành một thuật toán thuần túy O ( n log n ), bằng cách mô phỏng bộ nhớ có thể điều chỉnh bằng cây nhị phân cân bằng. Có những vấn đề không thể làm tốt hơn thế; Tôi không biết nếu họ hoàn toàn là lý thuyết.
Brian Campbell

3
Kết quả Pippenger đưa ra hai giả định quan trọng giới hạn phạm vi của nó: nó xem xét các tính toán "trực tuyến" hoặc "phản ứng" (không phải là mô hình thông thường của ánh xạ tính toán đầu vào hữu hạn cho một đầu ra duy nhất) và tính toán "tượng trưng" trong đó các đầu vào là chuỗi các nguyên tử, có thể được kiểm tra chỉ cho sự bằng nhau (nghĩa là việc giải thích đầu vào là cực kỳ nguyên thủy).
Chris Conway

2
Câu trả lời rất hay; Tôi muốn nói thêm rằng đối với các ngôn ngữ chức năng thuần túy, không có mô hình nào được thống nhất về tính phức tạp của máy tính, trong khi trong thế giới không tinh khiết, máy RAM chi phí đơn vị là tương đối chuẩn (vì vậy điều này khiến việc so sánh mọi thứ trở nên khó khăn hơn). Cũng lưu ý rằng giới hạn trên của chênh lệch Lg (N) trong thuần / không tinh khiết có thể được giải thích bằng trực giác rất dễ dàng bằng cách xem xét việc thực hiện các mảng trong một ngôn ngữ thuần túy (chi phí lg (n) cho mỗi thao tác (và bạn có được lịch sử)) .
dùng51568

4
Điểm quan trọng: Việc dịch một đặc tả chức năng thuần túy thành một triển khai chức năng thuần túy hiệu quả phức tạp hơn sẽ ít có lợi nếu cuối cùng bạn sẽ - tự động hoặc bằng tay - dịch nó thành mã không tinh khiết hiệu quả hơn. Tạp chất không phải là vấn đề lớn nếu bạn có thể giữ nó trong lồng, ví dụ như bằng cách khóa nó trong một chức năng không có tác dụng phụ bên ngoài.
Robin Green

44

Thực tế, có một số thuật toán và cấu trúc dữ liệu mà không có giải pháp chức năng thuần túy hiệu quả không có triệu chứng nào (có thể thực hiện được trong phép tính lambda thuần túy), ngay cả với sự lười biếng.

  • Công đoàn đã nói ở trên
  • Bảng băm
  • Mảng
  • Một số thuật toán đồ thị
  • ...

Tuy nhiên, chúng tôi giả định rằng trong các ngôn ngữ "bắt buộc", quyền truy cập vào bộ nhớ là O (1) trong khi về lý thuyết không thể bất thường (ví dụ như đối với kích thước sự cố không giới hạn) và quyền truy cập vào bộ nhớ trong bộ dữ liệu khổng lồ luôn là O (log n) , có thể được mô phỏng trong một ngôn ngữ chức năng.

Ngoài ra, chúng ta phải nhớ rằng thực sự tất cả các ngôn ngữ chức năng hiện đại đều cung cấp dữ liệu có thể thay đổi và Haskell thậm chí còn cung cấp nó mà không làm mất đi độ tinh khiết (đơn vị ST).


3
Nếu tập dữ liệu vừa với bộ nhớ vật lý, truy cập vào nó là O (1) ở chỗ có thể tìm thấy giới hạn trên tuyệt đối về lượng thời gian để đọc bất kỳ mục nào. Nếu tập dữ liệu không có, bạn đang nói về I / O và đó sẽ là yếu tố chi phối cho đến nay, tuy nhiên chương trình được viết.
Donal Fellows

Tất nhiên, tôi đang nói về các hoạt động truy cập vào bộ nhớ ngoài của O (log n). Tuy nhiên, trong mọi trường hợp tôi đã nói bs: bộ nhớ ngoài cũng có thể là O (1) có thể điều chỉnh được ...
jkff

2
Tôi nghĩ một trong những điều lớn nhất mà lập trình cấp bách đạt được so với lập trình chức năng là khả năng giữ các tham chiếu đến nhiều khía cạnh khác nhau của một trạng thái và tạo ra một trạng thái mới sao cho tất cả các tham chiếu đó chỉ ra các khía cạnh tương ứng của trạng thái mới. Sử dụng lập trình chức năng sẽ yêu cầu các hoạt động hội thảo trực tiếp được thay thế bằng các hoạt động tra cứu để tìm khía cạnh thích hợp của một phiên bản cụ thể của trạng thái tổng thể hiện tại.
supercat

Ngay cả truy cập bộ nhớ mô hình con trỏ (O (log n), nói một cách lỏng lẻo) cũng không thực tế về mặt vật lý ở quy mô cực lớn. Tốc độ ánh sáng giới hạn mức độ nhanh chóng các phần khác nhau của thiết bị máy tính có thể giao tiếp với nhau, trong khi hiện tại người ta tin rằng lượng thông tin tối đa có thể được lưu giữ trong một khu vực nhất định bị giới hạn bởi diện tích bề mặt của nó.
dfeuer

36

Bài viết này tuyên bố rằng các triển khai chức năng thuần túy đã biết của thuật toán tìm kết hợp đều có độ phức tạp tiệm cận tồi tệ hơn so với cái mà chúng xuất bản, có giao diện hoàn toàn chức năng nhưng sử dụng dữ liệu có thể thay đổi bên trong.

Thực tế là các câu trả lời khác cho rằng không bao giờ có sự khác biệt nào và ví dụ, "nhược điểm" duy nhất của mã chức năng thuần túy là nó có thể được song song hóa cho bạn ý tưởng về tính thông tin / tính khách quan của cộng đồng lập trình chức năng về những vấn đề này .

BIÊN TẬP:

Những bình luận dưới đây chỉ ra rằng một cuộc thảo luận thiên vị về những ưu và nhược điểm của lập trình chức năng thuần túy có thể không đến từ cộng đồng lập trình chức năng của Cameron. Điểm tốt. Có lẽ những người ủng hộ tôi thấy chỉ là, để trích dẫn một bình luận, không biết chữ.

Ví dụ, tôi nghĩ rằng bài đăng trên blog này được viết bởi một người có thể được cho là đại diện của cộng đồng lập trình chức năng, và vì đó là danh sách các điểm về đánh giá lười biếng, nên sẽ là một nơi tốt để đề cập đến bất kỳ nhược điểm nào. lười biếng và hoàn toàn có thể lập trình chức năng có thể có. Một vị trí tốt sẽ được thay thế cho những điều sau đây (đúng về mặt kỹ thuật, nhưng thiên vị đến mức không hài hước):

Nếu một hàm nghiêm ngặt có độ phức tạp O (f (n)) trong một ngôn ngữ nghiêm ngặt thì nó cũng có độ phức tạp O (f (n)) trong một ngôn ngữ lười biếng. Sao phải lo lắng? :)


4

Với một giới hạn trên cố định về việc sử dụng bộ nhớ, sẽ không có sự khác biệt.

Phác thảo bằng chứng: Với giới hạn trên cố định về việc sử dụng bộ nhớ, người ta sẽ có thể viết một máy ảo thực thi một tập lệnh bắt buộc với độ phức tạp tiệm cận giống như khi bạn thực sự thực thi trên máy đó. Điều này là như vậy bởi vì bạn có thể quản lý bộ nhớ có thể thay đổi dưới dạng cấu trúc dữ liệu liên tục, cho O (log (n)) đọc và ghi, nhưng với giới hạn trên cố định về mức sử dụng bộ nhớ, bạn có thể có một lượng bộ nhớ cố định, khiến chúng bị lỗi phân rã thành O (1). Do đó, việc triển khai chức năng có thể là phiên bản bắt buộc chạy trong triển khai chức năng của VM và do đó cả hai đều có cùng độ phức tạp tiệm cận.


6
Một giới hạn trên cố định về việc sử dụng bộ nhớ không phải là cách mọi người phân tích các loại điều này; bạn giả sử một bộ nhớ lớn, nhưng hữu hạn. Khi thực hiện một thuật toán, tôi quan tâm đến cách nó sẽ mở rộng từ đầu vào đơn giản nhất cho đến bất kỳ kích thước đầu vào tùy ý nào. Nếu bạn đặt giới hạn trên cố định cho việc sử dụng bộ nhớ, tại sao bạn cũng không đặt giới hạn trên cố định vào khoảng thời gian bạn sẽ cho phép tính toán và nói rằng mọi thứ đều là O (1)?
Brian Campbell

@Brian Campbell: Đó là sự thật. Tôi chỉ đề nghị rằng nếu bạn muốn, bạn có thể bỏ qua sự khác biệt trong yếu tố không đổi trong nhiều trường hợp trong thực tế. Người ta vẫn cần phải chú ý đến sự khác biệt khi thỏa hiệp giữa bộ nhớ và thời gian để đảm bảo rằng việc sử dụng bộ nhớ nhiều lần hơn m làm giảm thời gian chạy của bạn ít nhất là một yếu tố của nhật ký (m).
Brian

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.