Ngôn ngữ ứng dụng không hỗ trợ ghi thời gian liên tục? Nếu không, tai sao không?


7

Tôi đang đọc cuốn "Cẩm nang thiết kế thuật toán" của Steven Skiena, và trong một trong những Câu chuyện chiến tranh của ông ở trang 155, ông nói:

Hiệu quả là một thách thức lớn trong Mathematica, do mô hình tính toán ứng dụng của nó (nó không hỗ trợ các hoạt động ghi liên tục vào các mảng) và chi phí giải thích (trái ngược với biên dịch).

Tôi đã đọc SICP vì vậy tôi quen với sự khác biệt giữa ngôn ngữ ứng dụng và ngôn ngữ thông thường (nghĩa là ngôn ngữ đơn hàng bình thường trì hoãn việc đánh giá các đối số thủ tục cho đến khi chúng cần, trong khi các ngôn ngữ theo thứ tự ứng dụng đánh giá chúng ngay khi thủ tục được gọi). Nhưng câu của Skiena ở trên dường như liên kết ý tưởng về các ngôn ngữ theo thứ tự ứng dụng với ý tưởng viết mảng kém hơn thời gian không đổi. Tôi không nhớ Abelson và Sussman đề cập đến điều này trong văn bản của họ, vì vậy điều này gây ngạc nhiên.

Nếu đúng, những lý do ngầm nào cho lý do tại sao các ngôn ngữ theo thứ tự ứng dụng không ghi vào mảng trong thời gian liên tục? Tại sao thứ tự của vấn đề đánh giá trong việc xác định thời gian viết mảng?

Tôi cũng tò mò hiệu suất viết Big-O là gì trong trường hợp này, nhưng tôi tưởng tượng điều đó phụ thuộc vào việc thực hiện ngôn ngữ, vì vậy tôi sẽ bỏ qua câu hỏi đó trừ khi có câu trả lời dứt khoát.

Câu trả lời:


11

Skiena đã không nói "đơn đặt hàng ", chỉ "ứng dụng". Điều này đôi khi được sử dụng như một ý nghĩa như "hoàn toàn chức năng", hoặc một ngôn ngữ đánh giá thông qua việc áp dụng các chức năng trái ngược với việc thực hiện các lệnh thao túng trạng thái. Mathematica (ít nhất là ngày nay) không hoàn toàn là chức năng, nhưng có vẻ như nó khuyến khích mạnh mẽ một phong cách lập trình hoàn toàn chức năng.

Trong một ngôn ngữ chức năng thuần túy, chẳng hạn như Haskell, bạn áp dụng các hàm cho các giá trị và tạo ra các giá trị mới giống như trong toán học. Giống như toán học, trong một ngôn ngữ như vậy, các biến chỉ là tên của các giá trị. Nếu bạn nói "hãyx1", sau đó x1ở khắp mọi nơi có thể hoán đổi cho nhau. Thật vô nghĩa khi nói về "đột biến"x được 2 hơn là nói để thiết lập 1 được 2. Đối với mảng, điều này có nghĩa là để "cập nhật" một mảng, bạn xây dựng một mảng mới với các thay đổi. Rõ ràng, xây dựng toàn bộ một bản sao của một mảng ngoại trừ một mục bạn đang thay đổi là tốn kém. Đây gần như chắc chắn là vấn đề Skiena đang đề cập.

Theo hiểu biết của tôi, không có câu trả lời thỏa đáng nào cho vấn đề này. Đó là, không có cấu trúc dữ liệu chức năng thuần túy cung cấp tra cứu và cập nhật thời gian liên tục cho tất cả các phiên bản . Bạn có thể tạo các cấu trúc dữ liệu thuần túy (bên ngoài) để cung cấp tra cứu và cập nhật thời gian liên tục mới nhấtcác phiên bản, nhưng việc truy cập vào các phiên bản cũ hơn của mảng trở nên chậm hơn. Bạn có thể sử dụng các loại đơn hoặc loại duy nhất để thực thi việc sử dụng các mảng cho phép trình biên dịch sử dụng các bản cập nhật tại chỗ, nhưng điều này tương đương với việc sử dụng một cách tiếp cận bắt buộc (mặc dù theo cách thức được kiểm soát và có chứa). Trong thực tế, các lập trình viên trong các ngôn ngữ chức năng thuần túy đơn giản là không sử dụng mảng nhiều như các lập trình viên bắt buộc và khi họ sử dụng mảng, họ có xu hướng sử dụng các hoạt động hàng loạt. Nếu bạn định chạm vào mọi phần tử của một mảng, thì chi phí sao chép mảng là không đổi trên mỗi phần tử.

Mặc dù chắc chắn có những hạn chế của lập trình chức năng thuần túy khiến việc triển khai hiệu quả trở nên không rõ ràng, hầu hết thời gian chỉ là vấn đề áp dụng các kỹ thuật thiết kế và triển khai khác nhau so với điển hình / thực tế trong các ngôn ngữ mệnh lệnh. Độ tinh khiết cũng đảm bảo rằng làm cho nhiều thiết kế, tối ưu hóa, và đôi khi toàn bộ công nghệ thực tế. Đối với lần đầu tiên, hầu hết các cấu trúc dữ liệu chức năng thuần túy được thiết kế để chia sẻ càng nhiều càng tốt. Đối với lần thứ hai, một kỹ thuật chính để cải thiện hiệu suất của các hoạt động hàng loạt là hợp nhất kết hợp nhiều hoạt động hàng loạt thành một để tránh các trung gian. Cuối cùng,


3
Ngoài ra, một cấu trúc dữ liệu cây với một quạt lớn là "thực tế không đổi". Ví dụ, một cây 64 ary với 4 tỷ phần tử nhiều nhất là 7 bước từ gốc đến lá. Đó là 7 tra cứu con trỏ trái ngược với 1 cho một mảng. Không tệ lắm. Rich Hickey, nhà thiết kế của Clojure, là thích nói: vectơ Clojure của là O (log_32 n), và 32 là rất quan trọng, bởi vì đối với nhiều kích cỡ vấn đề thực tế, hằng làm vấn đề. O (log_X n) cho X> 30 khá gần với O (1) với số tiền thậm chí rất lớn n ~ tỷ.
Jörg W Mittag

4
@ JörgWMittag Và ngay cả trong một thế giới bắt buộc, truy cập mảng O (1) là một lời nói dối nhỏ, vì phần cứng phải đi qua cổng O (log n) để truy cập vào một ô nhớ. Mô hình chi phí RAM thuận tiện cho phép truy cập O (1) và chúng tôi chấp nhận điều đó chỉ vì nó đủ gần để thực hành (dù sao chúng tôi không có hàng trăm byte bộ nhớ).
chi

@ JörgWMittag: Có lẽ tôi là sự hiểu lầm, nhưng - trong một cấu trúc cây hoàn toàn chức năng như bạn mô tả, một get sẽ là 7 lần như đắt tiền, nhưng một bản cập nhật sẽ là 7x64 lần tốn kém, thậm chí chiết khấu chi phí phân bổ tất cả các mới mảng. Trừ khi tỷ lệ đọc để viết là rất cao, đó không phải là một sự đánh đổi tầm thường.
ruakh
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.