Tôi thừa nhận thiên vị khi áp dụng các khái niệm như vậy trong C ++ bởi ngôn ngữ và bản chất của nó, cũng như miền của tôi và thậm chí cả cách chúng ta sử dụng ngôn ngữ. Nhưng với những điều này, tôi nghĩ rằng các thiết kế bất biến là khía cạnh ít thú vị nhất khi nói đến việc gặt hái một số lợi ích liên quan đến lập trình chức năng, như an toàn luồng, dễ suy luận về hệ thống, tìm cách sử dụng lại nhiều hơn cho các chức năng (và tìm kiếm chúng ta có thể kết hợp chúng theo bất kỳ thứ tự nào mà không có những bất ngờ khó chịu), v.v.
Lấy ví dụ đơn giản về C ++ này (phải thừa nhận là không được tối ưu hóa cho đơn giản để tránh lúng túng trước bất kỳ chuyên gia xử lý ảnh nào ngoài đó):
// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
{
Image dst(new_w, new_h);
for (int y=0; y < new_h; ++y)
{
for (int x=0; x < new_w; ++x)
dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
}
return dst;
}
Mặc dù việc thực hiện chức năng đó làm thay đổi trạng thái cục bộ (và tạm thời) dưới dạng hai biến truy cập và hình ảnh cục bộ tạm thời thành đầu ra, nhưng nó không có tác dụng phụ bên ngoài. Nó nhập một hình ảnh và xuất ra một hình ảnh mới. Chúng ta có thể đa luồng nó với nội dung trái tim của chúng ta. Thật dễ dàng để lý do về, dễ dàng để kiểm tra kỹ lưỡng. Nó an toàn ngoại lệ vì nếu có bất cứ thứ gì ném, hình ảnh mới sẽ tự động bị loại bỏ và chúng tôi không phải lo lắng về việc khôi phục các tác dụng phụ bên ngoài (không có hình ảnh bên ngoài nào được sửa đổi ngoài phạm vi của chức năng, có thể nói).
Tôi thấy rất ít để đạt được, và có khả năng bị mất nhiều, bằng cách làm cho Image
bất biến trong bối cảnh trên, trong C ++, ngoại trừ có khả năng làm cho chức năng trên trở nên khó sử dụng hơn và có thể kém hiệu quả hơn một chút.
Độ tinh khiết
Vì vậy, các hàm thuần túy (không có tác dụng phụ bên ngoài ) rất thú vị đối với tôi và tôi nhấn mạnh tầm quan trọng của việc thường xuyên ưu tiên chúng cho các thành viên trong nhóm ngay cả trong C ++. Nhưng các thiết kế bất biến, được áp dụng chỉ là bối cảnh và sắc thái thường không có gì thú vị đối với tôi, vì tính chất bắt buộc của ngôn ngữ, nó thường hữu ích và thiết thực để có thể biến đổi một số đối tượng tạm thời cục bộ trong quá trình hiệu quả (cả hai cho nhà phát triển và phần cứng) thực hiện một chức năng thuần túy.
Sao chép giá rẻ của cấu trúc khổng lồ
Thuộc tính hữu ích thứ hai mà tôi tìm thấy là khả năng sao chép một cách rẻ tiền các cấu trúc dữ liệu thực sự khổng lồ xung quanh khi chi phí thực hiện, như thường được phát sinh để làm cho các hàm thuần túy có tính chất đầu vào / đầu ra nghiêm ngặt của chúng, sẽ không tầm thường. Đây sẽ không phải là cấu trúc nhỏ có thể phù hợp với ngăn xếp. Chúng sẽ là những cấu trúc to lớn, giống như toàn bộ Scene
cho một trò chơi video.
Trong trường hợp đó, việc sao chép có thể ngăn chặn các cơ hội song song hiệu quả, bởi vì có thể khó song song hóa vật lý và kết xuất hiệu quả mà không khóa và tắc nghẽn lẫn nhau nếu vật lý đang làm biến đổi cảnh mà trình kết xuất đang cố gắng vẽ, đồng thời có vật lý sâu sao chép toàn bộ cảnh trò chơi xung quanh chỉ để xuất một khung hình với vật lý được áp dụng có thể không hiệu quả như nhau. Tuy nhiên, nếu hệ thống vật lý là 'thuần khiết' theo nghĩa là nó chỉ nhập vào một cảnh và xuất ra một cảnh mới với vật lý được áp dụng, và độ tinh khiết như vậy không phải trả giá bằng việc sao chép thiên văn trên cao, nó có thể hoạt động một cách an toàn song song với renderer mà không cần chờ đợi người khác.
Vì vậy, khả năng sao chép giá rẻ dữ liệu thực sự khổng lồ của trạng thái ứng dụng của bạn xung quanh và xuất ra các phiên bản mới, được sửa đổi với chi phí tối thiểu để xử lý và sử dụng bộ nhớ có thể thực sự mở ra những cánh cửa mới cho sự song song và hiệu quả, và tôi tìm thấy nhiều bài học để học từ cách cấu trúc dữ liệu liên tục được thực hiện. Nhưng bất cứ điều gì chúng ta tạo ra bằng những bài học như vậy không cần phải hoàn toàn bền bỉ, hoặc cung cấp các giao diện bất biến (ví dụ, nó có thể sử dụng bản sao trên văn bản, hoặc "trình tạo / tạm thời"), để đạt được khả năng này là rẻ mạt để sao chép xung quanh và sửa đổi chỉ các phần của bản sao mà không tăng gấp đôi sử dụng bộ nhớ và truy cập bộ nhớ trong nhiệm vụ tìm kiếm sự song song và tinh khiết trong các chức năng / hệ thống / đường ống của chúng tôi.
Bất biến
Cuối cùng, có sự bất biến mà tôi cho là ít thú vị nhất trong ba thứ này, nhưng nó có thể thực thi, bằng nắm đấm sắt, khi các thiết kế đối tượng nhất định không được sử dụng làm tạm thời cục bộ cho một chức năng thuần túy, và thay vào đó trong một bối cảnh rộng lớn hơn, có giá trị loại "độ tinh khiết ở mức đối tượng", vì trong tất cả các phương thức không còn gây ra tác dụng phụ bên ngoài (không còn biến đổi các biến thành viên bên ngoài phạm vi cục bộ ngay lập tức của phương thức).
Và trong khi tôi coi nó là thứ ít thú vị nhất trong ba ngôn ngữ như C ++, thì nó chắc chắn có thể đơn giản hóa việc kiểm tra và an toàn luồng và lý luận của các đối tượng không tầm thường. Chẳng hạn, nó có thể giảm tải để đảm bảo rằng một đối tượng không thể được cung cấp bất kỳ kết hợp trạng thái duy nhất nào bên ngoài hàm tạo của nó, và chúng ta có thể tự do chuyển nó xung quanh, thậm chí bằng tham chiếu / con trỏ mà không cần dựa vào hằng số và đọc chỉ các trình lặp và xử lý và như vậy, trong khi đảm bảo (tốt, ít nhất là nhiều nhất có thể trong ngôn ngữ) rằng nội dung ban đầu của nó sẽ không bị thay đổi.
Nhưng tôi thấy đây là thuộc tính ít thú vị nhất vì hầu hết các đối tượng tôi thấy có lợi khi được sử dụng tạm thời, ở dạng có thể thay đổi, để thực hiện một hàm thuần túy (hoặc thậm chí là một khái niệm rộng hơn, như một "hệ thống thuần túy" có thể là một đối tượng hoặc chuỗi Các chức năng với hiệu quả cuối cùng chỉ đơn thuần là nhập một cái gì đó và xuất ra một cái gì đó mới mà không chạm vào bất cứ thứ gì khác), và tôi nghĩ rằng sự bất biến được đưa đến cực đoan trong một ngôn ngữ chủ yếu là một mục tiêu khá phản tác dụng. Tôi sẽ áp dụng nó một cách tiết kiệm cho các phần của codebase, nơi nó thực sự giúp ích nhiều nhất.
Cuối cùng:
[...] Dường như các cấu trúc dữ liệu liên tục không đủ để xử lý các tình huống trong đó một luồng thực hiện thay đổi có thể nhìn thấy đối với các luồng khác. Đối với điều này, dường như chúng ta phải sử dụng các thiết bị như nguyên tử, tài liệu tham khảo, bộ nhớ giao dịch phần mềm hoặc thậm chí các khóa cổ điển và cơ chế đồng bộ hóa.
Đương nhiên nếu thiết kế của bạn yêu cầu sửa đổi (theo nghĩa thiết kế cuối người dùng) để hiển thị đồng thời nhiều luồng khi chúng xảy ra, chúng tôi sẽ quay lại đồng bộ hóa hoặc ít nhất là bảng vẽ để tìm ra một số cách tinh vi để xử lý vấn đề này ( Tôi đã thấy một số ví dụ rất công phu được sử dụng bởi các chuyên gia xử lý các loại vấn đề này trong lập trình chức năng).
Nhưng tôi đã tìm thấy, một khi bạn có được kiểu sao chép và khả năng tạo ra các phiên bản sửa đổi một phần của các cấu trúc khổng lồ bẩn thỉu, như bạn có thể nhận được với các cấu trúc dữ liệu liên tục như một ví dụ, nó thường mở ra rất nhiều cánh cửa và cơ hội bạn có thể trước đây không nghĩ đến việc song song mã có thể chạy hoàn toàn độc lập với nhau trong một đường ống song song I / O nghiêm ngặt. Ngay cả khi một số phần của thuật toán phải có bản chất nối tiếp, bạn có thể trì hoãn việc xử lý thành một luồng duy nhất nhưng thấy rằng việc dựa vào các khái niệm này đã mở ra cánh cửa dễ dàng, và không phải lo lắng, song song 90% công việc nặng nhọc, ví dụ