Câu hỏi gồm hai phần. Đầu tiên là khái niệm. Phần tiếp theo xem xét câu hỏi tương tự một cách cụ thể hơn trong Scala.
- Việc chỉ sử dụng cấu trúc dữ liệu bất biến trong ngôn ngữ lập trình có làm cho việc triển khai các thuật toán / logic nhất định vốn tính toán cao hơn trong thực tế không? Điều này rút ra một thực tế rằng tính bất biến là nguyên lý cốt lõi của các ngôn ngữ chức năng thuần túy. Có những yếu tố nào khác tác động đến điều này không?
- Hãy lấy một ví dụ cụ thể hơn. Quicksort thường được dạy và triển khai bằng cách sử dụng các phép toán có thể thay đổi trên cấu trúc dữ liệu trong bộ nhớ. Làm cách nào để triển khai một thứ như vậy theo cách chức năng TINH KHIẾT với chi phí lưu trữ và tính toán có thể so sánh được với phiên bản có thể thay đổi. Cụ thể là trong Scala. Tôi đã bao gồm một số điểm chuẩn thô bên dưới.
Thêm chi tiết:
Tôi đến từ nền tảng lập trình mệnh lệnh (C ++, Java). Tôi đã tìm hiểu về lập trình chức năng, cụ thể là Scala.
Một số nguyên tắc cơ bản của lập trình hàm thuần túy:
- Chức năng là công dân hạng nhất.
- Các hàm không có tác dụng phụ và do đó các đối tượng / cấu trúc dữ liệu là bất biến .
Mặc dù các JVM hiện đại cực kỳ hiệu quả với việc tạo đối tượng và việc thu gom rác rất rẻ đối với các đối tượng tồn tại trong thời gian ngắn, nhưng có lẽ vẫn tốt hơn là giảm thiểu việc tạo đối tượng đúng không? Ít nhất là trong một ứng dụng đơn luồng, nơi đồng thời và khóa không phải là một vấn đề. Vì Scala là một mô hình lai, người ta có thể chọn viết mã mệnh lệnh với các đối tượng có thể thay đổi nếu cần thiết. Nhưng, là một người đã dành nhiều năm cố gắng tái sử dụng các đối tượng và giảm thiểu phân bổ. Tôi muốn hiểu rõ về trường phái tư tưởng thậm chí không cho phép điều đó.
Như một trường hợp cụ thể, tôi hơi ngạc nhiên với đoạn mã này trong hướng dẫn 6 này . Nó có một phiên bản Java của Quicksort theo sau là một triển khai Scala trông giống nhau.
Đây là nỗ lực của tôi để đánh giá việc triển khai. Tôi chưa làm hồ sơ chi tiết. Tuy nhiên, tôi đoán là phiên bản Scala chậm hơn vì số lượng đối tượng được phân bổ là tuyến tính (một đối tượng cho mỗi lần gọi đệ quy). Có cách nào để tối ưu hóa cuộc gọi đuôi có thể phát huy tác dụng không? Nếu tôi đúng, Scala hỗ trợ tối ưu hóa cuộc gọi đuôi cho các cuộc gọi tự đệ quy. Vì vậy, nó chỉ nên được giúp đỡ nó. Tôi đang sử dụng Scala 2.8.
Phiên bản Java
public class QuickSortJ {
public static void sort(int[] xs) {
sort(xs, 0, xs.length -1 );
}
static void sort(int[] xs, int l, int r) {
if (r >= l) return;
int pivot = xs[l];
int a = l; int b = r;
while (a <= b){
while (xs[a] <= pivot) a++;
while (xs[b] > pivot) b--;
if (a < b) swap(xs, a, b);
}
sort(xs, l, b);
sort(xs, a, r);
}
static void swap(int[] arr, int i, int j) {
int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
}
}
Phiên bản Scala
object QuickSortS {
def sort(xs: Array[Int]): Array[Int] =
if (xs.length <= 1) xs
else {
val pivot = xs(xs.length / 2)
Array.concat(
sort(xs filter (pivot >)),
xs filter (pivot ==),
sort(xs filter (pivot <)))
}
}
Mã Scala để so sánh các triển khai
import java.util.Date
import scala.testing.Benchmark
class BenchSort(sortfn: (Array[Int]) => Unit, name:String) extends Benchmark {
val ints = new Array[Int](100000);
override def prefix = name
override def setUp = {
val ran = new java.util.Random(5);
for (i <- 0 to ints.length - 1)
ints(i) = ran.nextInt();
}
override def run = sortfn(ints)
}
val benchImmut = new BenchSort( QuickSortS.sort , "Immutable/Functional/Scala" )
val benchMut = new BenchSort( QuickSortJ.sort , "Mutable/Imperative/Java " )
benchImmut.main( Array("5"))
benchMut.main( Array("5"))
Các kết quả
Thời gian tính bằng mili giây cho năm lần chạy liên tiếp
Immutable/Functional/Scala 467 178 184 187 183
Mutable/Imperative/Java 51 14 12 12 12
O(n)
danh sách. Đó là ngắn hơn so với phiên bản giả mặc dù;)