Lập trình hàm: ý tưởng đúng về đồng thời và trạng thái?


21

Những người đề xuất FP đã tuyên bố rằng đồng thời là dễ dàng bởi vì mô hình của họ tránh được trạng thái có thể thay đổi. Tôi không hiểu

Hãy tưởng tượng chúng ta đang tạo ra một dungeon thu thập dữ liệu nhiều người chơi (một roguelike) bằng cách sử dụng FP trong đó chúng ta nhấn mạnh các hàm thuần túy và các cấu trúc dữ liệu bất biến. Chúng tôi tạo ra một hầm ngục bao gồm các phòng, hành lang, anh hùng, quái vật và cướp bóc. Thế giới của chúng ta thực sự là một biểu đồ đối tượng của các cấu trúc và các mối quan hệ của chúng. Khi mọi thứ thay đổi, đại diện của chúng ta về thế giới được sửa đổi để phản ánh những thay đổi đó. Anh hùng của chúng ta giết một con chuột, nhặt một thanh kiếm ngắn, v.v.

Đối với tôi thế giới (thực tế hiện tại) mang ý tưởng về nhà nước này và tôi đang thiếu cách mà FP vượt qua điều này. Khi anh hùng của chúng ta hành động, các chức năng sửa đổi trạng thái của thế giới. Dường như mọi quyết định (AI hoặc con người) cần phải dựa trên trạng thái của thế giới như hiện tại. Chúng ta sẽ cho phép đồng thời ở đâu? Chúng ta không thể có nhiều quy trình đồng thời củng cố trạng thái của thế giới kẻo một quá trình dựa trên kết quả của nó trên một số trạng thái đã hết hạn. Tôi cảm thấy rằng tất cả các điều khiển nên xảy ra trong một vòng điều khiển duy nhất để chúng tôi luôn xử lý trạng thái hiện tại được biểu thị bằng biểu đồ đối tượng hiện tại của chúng ta về thế giới.

Rõ ràng có những tình huống hoàn toàn phù hợp cho sự tương tranh (tức là khi xử lý các tác vụ bị cô lập có trạng thái độc lập với nhau).

Tôi không thấy làm thế nào đồng thời là hữu ích trong ví dụ của tôi và đó có thể là vấn đề. Tôi có thể đang trình bày sai về yêu cầu nào đó.

Ai đó có thể đại diện tốt hơn cho tuyên bố này?


1
Bạn đang đề cập đến trạng thái chia sẻ; trạng thái chia sẻ sẽ luôn luôn là như vậy và sẽ luôn yêu cầu một số hình thức đồng bộ hóa, hình thức ưa thích của những người FP thuần túy là STM cho phép bạn coi bộ nhớ chia sẻ là bộ nhớ cục bộ bằng cách tạo một lớp trừu tượng trên nó để tạo ra giao dịch truy cập. điều kiện được xử lý tự động. Một kỹ thuật khác cho bộ nhớ chia sẻ là thông điệp đi qua thay vì có bộ nhớ chia sẻ, bạn có bộ nhớ cục bộ và kiến ​​thức về các diễn viên khác để hỏi về bộ nhớ cục bộ của họ
Jimmy Hoffa

1
Vì vậy, ... bạn đang hỏi làm thế nào đồng thời trạng thái chia sẻ dễ dàng giúp quản lý trạng thái trong một ứng dụng đơn luồng? Mặt khác, ví dụ của bạn rõ ràng cho vay đồng thời về mặt khái niệm (một luồng cho mỗi thực thể do AI kiểm soát) cho dù nó có được thực hiện theo cách đó hay không. Tôi bối rối những gì bạn đang hỏi ở đây.
CA McCann

1
trong một từ, dây kéo
jk.

2
Mỗi đối tượng sẽ có quan điểm riêng của họ về thế giới. Sẽ có sự thống nhất cuối cùng . Có lẽ đó cũng là cách mọi thứ hoạt động trong "thế giới thực" của chúng ta với chức năng sóng sụp đổ .
herzmeister

1
Bạn có thể thấy "retrogames hoàn toàn chức năng" thú vị: prog21.dadgum.com/23.html
user802500

Câu trả lời:


15

Tôi sẽ cố gắng gợi ý về câu trả lời. Đây không phải là một câu trả lời, chỉ là một minh họa giới thiệu. Câu trả lời của @ jk chỉ ra đồ thật, khóa kéo.

Hãy tưởng tượng bạn có một cấu trúc cây bất biến. Bạn muốn thay đổi một nút bằng cách chèn một con. Kết quả là bạn có được một cây hoàn toàn mới.

Nhưng hầu hết cây mới giống hệt cây cũ. Một triển khai thông minh sẽ sử dụng lại hầu hết các đoạn cây, con trỏ định tuyến xung quanh nút bị thay đổi:

Từ Wikipedia

Cuốn sách của Okasaki có đầy đủ các ví dụ như thế này.

Vì vậy, tôi cho rằng bạn có thể thay đổi hợp lý các phần nhỏ trong thế giới trò chơi của mình mỗi lần di chuyển (nhặt một đồng xu) và chỉ thực sự thay đổi các phần nhỏ trong cấu trúc dữ liệu thế giới của bạn (ô nơi nhặt đồng xu). Các bộ phận chỉ thuộc về các tiểu bang trong quá khứ sẽ được thu gom rác kịp thời.

Điều này có thể có một số xem xét trong việc thiết kế cấu trúc thế giới trò chơi dữ liệu theo một cách thích hợp. Thật không may, tôi không có chuyên gia trong những vấn đề này. Chắc chắn nó phải là một cái gì đó khác với ma trận NxM mà người ta sẽ sử dụng làm cấu trúc dữ liệu có thể thay đổi. Có lẽ nó nên bao gồm các mảnh nhỏ hơn (hành lang? Các ô riêng lẻ?) Chỉ vào nhau, như các nút cây làm.


3
+1: Để chỉ vào cuốn sách của Okasaki. Tôi chưa đọc nó nhưng nó nằm trong danh sách cần làm của tôi. Tôi nghĩ những gì bạn mô tả là giải pháp chính xác. Thay vào đó, bạn có thể xem xét các loại duy nhất (Clean, en.wikipedia.org/wiki/Uniquety_type ): sử dụng loại loại này bạn có thể cập nhật triệt để các đối tượng dữ liệu trong khi vẫn giữ được độ trong suốt của tham chiếu.
Giorgio

Có một lợi ích cho các mối quan hệ được xác định thông qua tham chiếu gián tiếp thông qua các khóa hoặc id? Đó là, tôi đã nghĩ rằng việc chạm ít hơn thực tế của cấu trúc này sang cấu trúc khác sẽ cần ít sửa đổi hơn đối với cấu trúc thế giới khi có sự thay đổi. Hay là kỹ thuật này không thực sự được sử dụng trong FP?
Mario T. Lanza

9

Câu trả lời của 9000 là một nửa câu trả lời, cấu trúc dữ liệu liên tục cho phép bạn sử dụng lại các phần không thay đổi.

Bạn có thể đã suy nghĩ tuy nhiên "này, nếu tôi muốn thay đổi gốc của cây thì sao?" vì nó đứng với ví dụ đã cho rằng bây giờ có nghĩa là thay đổi tất cả các nút. Đây là nơi dây kéo đến để giải cứu. Chúng cho phép phần tử tại tiêu điểm được thay đổi trong O (1) và tiêu điểm có thể được di chuyển bất cứ nơi nào trong cấu trúc.

Điểm khác với khóa kéo là Zipper tồn tại khá nhiều loại dữ liệu bạn muốn


Tôi sợ rằng tôi sẽ mất một chút thời gian để đào sâu vào "dây kéo" vì tôi ở ngoài rìa bất kỳ chỉ khám phá FP. Tôi không có kinh nghiệm với Haskell.
Mario T. Lanza

Tôi sẽ cố gắng thêm một ví dụ sau hôm nay
jk.

4

Các chương trình kiểu chức năng tạo ra rất nhiều cơ hội như thế để sử dụng đồng thời. Bất cứ khi nào bạn chuyển đổi hoặc lọc hoặc tổng hợp một bộ sưu tập, và mọi thứ là thuần túy hoặc không thay đổi, sẽ có cơ hội cho hoạt động được tăng tốc bởi sự tương tranh.

Ví dụ: giả sử bạn thực hiện các quyết định AI độc lập với nhau và không theo thứ tự cụ thể. Họ không thay phiên nhau, tất cả họ đưa ra quyết định đồng thời và sau đó thế giới tiến bộ. Mã có thể trông như thế này:

func MakeMonsterDecision curWorldState monster =
    ...
    ...
    return monsterDecision

func NextWorldState curWorldState =
    ...
    let monsterMakeDecisionForCurrentState = MakeMonsterDecision curWorldState
    let monsterDecisions = List.map monsterMakeDecisionForCurrentState activeMonsters
    ...
    return newWorldState

Bạn có một chức năng để tính toán những gì một con quái vật sẽ làm cho một trạng thái thế giới, và áp dụng nó cho mọi quái vật như là một phần của tính toán trạng thái thế giới tiếp theo. Đây là một điều tự nhiên phải làm trong một ngôn ngữ chức năng và trình biên dịch có thể tự do thực hiện song song việc áp dụng nó cho mọi quái vật.

Trong một ngôn ngữ bắt buộc, bạn sẽ có nhiều khả năng lặp đi lặp lại trên mọi quái vật, áp dụng hiệu ứng của chúng cho thế giới. Làm điều đó dễ dàng hơn bởi vì bạn không muốn đối phó với việc nhân bản hoặc răng cưa phức tạp. Trình biên dịch không thể thực hiện các tính toán quái vật song song trong trường hợp đó, bởi vì các quyết định quái vật sớm ảnh hưởng đến các quyết định quái vật sau này.


Điều đó giúp khá nhiều. Tôi có thể thấy làm thế nào trong một trò chơi sẽ có lợi ích lớn khi có những con quái vật đồng thời quyết định những gì chúng sẽ làm tiếp theo.
Mario T. Lanza

4

Lắng nghe một vài cuộc nói chuyện của Rich Hickey - đặc biệt là cuộc nói chuyện này - đã làm giảm bớt sự nhầm lẫn của tôi. Trong một lần, ông chỉ ra rằng không sao khi các quy trình đồng thời có thể không có trạng thái hiện tại nhất. Tôi cần nghe đó. Điều tôi gặp khó khăn trong việc tiêu hóa là các chương trình sẽ thực sự ổn với các quyết định dựa trên các ảnh chụp nhanh về thế giới đã bị thay thế bởi các chương trình mới hơn. Tôi cứ tự hỏi làm thế nào mà FP đồng thời xoay quanh vấn đề dựa trên các quyết định dựa trên tình trạng cũ.

Trong một ứng dụng ngân hàng, chúng tôi sẽ không bao giờ muốn đưa ra quyết định dựa trên ảnh chụp nhanh trạng thái đã được thay thế bởi một cái mới hơn (đã xảy ra rút tiền).

Sự đồng thời đó là dễ dàng bởi vì mô hình FP tránh trạng thái có thể thay đổi là một yêu cầu kỹ thuật không cố gắng nói bất cứ điều gì về giá trị logic của các quyết định dựa trên trạng thái tiềm năng cũ. Cuối cùng, FP vẫn thay đổi mô hình nhà nước. Không có xung quanh này.


0

Những người đề xuất FP đã tuyên bố rằng đồng thời là dễ dàng bởi vì mô hình của họ tránh được trạng thái có thể thay đổi. Tôi không hiểu

Tôi muốn đưa ra câu hỏi chung chung này với tư cách là một người có chức năng tân sinh nhưng đã bị ảnh hưởng đến nhãn cầu của tôi trong nhiều năm qua và muốn giảm thiểu chúng, vì mọi lý do, bao gồm dễ dàng hơn (hoặc cụ thể là "an toàn hơn," ít xảy ra lỗi ") đồng thời. Khi tôi liếc qua các đồng nghiệp chức năng của mình và những gì họ đang làm, cỏ có vẻ hơi xanh hơn và có mùi thơm hơn, ít nhất là về vấn đề này.

Thuật toán nối tiếp

Điều đó nói rằng, về ví dụ cụ thể của bạn, nếu bản chất của bạn là vấn đề nối tiếp và B không thể được thực thi cho đến khi A kết thúc, về mặt khái niệm bạn không thể chạy A và B song song bất kể điều gì. Bạn phải tìm cách phá vỡ sự phụ thuộc thứ tự như trong câu trả lời của mình dựa trên việc thực hiện các bước di chuyển song song bằng trạng thái trò chơi cũ hoặc sử dụng cấu trúc dữ liệu cho phép các phần của nó được sửa đổi độc lập để loại bỏ sự phụ thuộc đơn hàng như đề xuất trong các câu trả lời khác hoặc một cái gì đó thuộc loại này. Nhưng chắc chắn có một phần các vấn đề thiết kế khái niệm như thế này, nơi bạn không nhất thiết chỉ là đa luồng mọi thứ một cách dễ dàng bởi vì mọi thứ là bất biến. Một số thứ sẽ được nối tiếp trong tự nhiên cho đến khi bạn tìm ra một cách thông minh để phá vỡ sự phụ thuộc đơn hàng, nếu điều đó thậm chí có thể.

Đồng thời dễ dàng hơn

Điều đó nói rằng, có nhiều trường hợp chúng tôi không song song hóa các chương trình liên quan đến tác dụng phụ ở những nơi có khả năng cải thiện hiệu suất đáng kể chỉ vì khả năng nó có thể không an toàn cho chuỗi. Một trong những trường hợp loại bỏ trạng thái có thể thay đổi (hay cụ thể hơn là tác dụng phụ bên ngoài) giúp ích rất nhiều vì tôi thấy nó biến "có thể hoặc không an toàn cho luồng" thành "chắc chắn an toàn cho luồng" .

Để làm cho tuyên bố đó cụ thể hơn một chút, hãy xem xét rằng tôi giao cho bạn một nhiệm vụ để thực hiện một chức năng sắp xếp trong C chấp nhận một bộ so sánh và sử dụng điều đó để sắp xếp một mảng các phần tử. Nó có nghĩa là khá khái quát nhưng tôi sẽ cho bạn một giả định dễ dàng rằng nó sẽ được sử dụng để chống lại các yếu tố đầu vào có quy mô như vậy (hàng triệu yếu tố trở lên) mà chắc chắn sẽ có ích khi luôn sử dụng triển khai đa luồng. Bạn có thể đa chức năng sắp xếp của bạn?

Vấn đề là bạn không thể bởi vì các bộ so sánh gọi chức năng sắp xếp của bạn có thểgây ra tác dụng phụ trừ khi bạn biết cách chúng được thực hiện (hoặc ít nhất là được ghi lại) cho tất cả các trường hợp có thể xảy ra mà không thể làm suy giảm chức năng. Một bộ so sánh có thể làm một cái gì đó kinh tởm như sửa đổi một biến toàn cục bên trong theo cách phi nguyên tử. 99.9999% bộ so sánh có thể không làm điều này, nhưng chúng tôi vẫn không thể đa chức năng tổng quát hóa chức năng này đơn giản chỉ vì 0,00001% trường hợp có thể gây ra tác dụng phụ. Kết quả là bạn có thể phải cung cấp cả hàm sắp xếp một luồng và đa luồng và chuyển trách nhiệm cho các lập trình viên sử dụng nó để quyết định sử dụng cái nào dựa trên sự an toàn của luồng. Và mọi người vẫn có thể sử dụng phiên bản một luồng và bỏ lỡ các cơ hội để đa luồng vì họ cũng có thể không chắc chắn liệu bộ so sánh có an toàn cho luồng hay không,

Có rất nhiều trí tuệ có thể tham gia vào việc hợp lý hóa sự an toàn của mọi thứ mà không cần ném khóa ở mọi nơi có thể biến mất nếu chúng ta chỉ đảm bảo chắc chắn rằng các chức năng sẽ không gây ra tác dụng phụ cho hiện tại và tương lai. Và có nỗi sợ: nỗi sợ thực tế, bởi vì bất kỳ ai phải gỡ lỗi một điều kiện cuộc đua quá nhiều lần có lẽ sẽ do dự về việc đa luồng bất cứ điều gì mà họ không thể chắc chắn 110% là an toàn theo luồng và sẽ vẫn như vậy. Ngay cả đối với những người hoang tưởng nhất (trong đó tôi có lẽ ít nhất là đường biên giới), chức năng thuần túy mang lại cảm giác nhẹ nhõm và tự tin mà chúng ta có thể gọi nó một cách an toàn song song.

Và đó là một trong những trường hợp chính mà tôi thấy nó rất có lợi nếu bạn có thể đảm bảo chắc chắn rằng các chức năng đó là an toàn theo luồng mà bạn có được với các ngôn ngữ chức năng thuần túy. Khác là các ngôn ngữ chức năng thường thúc đẩy việc tạo các chức năng không có tác dụng phụ ở nơi đầu tiên. Ví dụ, họ có thể cung cấp cho bạn các cấu trúc dữ liệu liên tục trong đó khá hợp lý để nhập cấu trúc dữ liệu lớn và sau đó xuất một cấu trúc hoàn toàn mới chỉ với một phần nhỏ thay đổi so với ban đầu mà không cần chạm vào bản gốc. Những người làm việc mà không có cấu trúc dữ liệu như vậy có thể muốn sửa đổi chúng trực tiếp và mất một số an toàn luồng trên đường đi.

Tác dụng phụ

Điều đó nói rằng, tôi không đồng ý với một phần với tất cả sự tôn trọng với những người bạn chức năng của tôi (người mà tôi nghĩ là rất tuyệt):

[...] Bởi vì mô hình của họ tránh được trạng thái đột biến.

Nó không nhất thiết là bất biến làm cho đồng thời trở nên thiết thực như tôi thấy. Đó là chức năng tránh gây ra tác dụng phụ. Nếu một hàm nhập một mảng để sắp xếp, sao chép nó và sau đó biến đổi bản sao để sắp xếp nội dung của nó và xuất ra bản sao, thì nó vẫn an toàn như luồng khi làm việc với một số loại mảng bất biến ngay cả khi bạn chuyển cùng một đầu vào mảng đến nó từ nhiều chủ đề. Vì vậy, tôi nghĩ rằng vẫn còn một nơi cho các loại có thể thay đổi trong việc tạo mã rất thân thiện đồng thời, có thể nói, mặc dù có rất nhiều lợi ích bổ sung cho các loại bất biến, bao gồm các cấu trúc dữ liệu liên tục mà tôi sử dụng không nhiều cho các thuộc tính bất biến của chúng nhưng loại bỏ chi phí phải sao chép sâu mọi thứ để tạo ra các chức năng không có tác dụng phụ.

Và thường có chi phí cao để tạo ra các chức năng không có tác dụng phụ dưới dạng xáo trộn và sao chép một số dữ liệu bổ sung, có thể là một mức độ gián tiếp bổ sung và có thể là một số GC trên các phần của cấu trúc dữ liệu liên tục, nhưng tôi nhìn vào một người bạn của tôi có một cỗ máy 32 lõi và tôi nghĩ việc trao đổi có lẽ đáng giá nếu chúng ta có thể tự tin làm nhiều việc hơn song song.


1
"Trạng thái có thể thay đổi" luôn có nghĩa là trạng thái ở cấp ứng dụng, không phải là cấp độ thủ tục. Loại bỏ các con trỏ và truyền tham số dưới dạng giá trị sao chép là một trong những kỹ thuật được đưa vào FP. Nhưng bất kỳ chức năng hữu ích nào cũng phải biến đổi trạng thái ở một mức nào đó - điểm của lập trình chức năng là đảm bảo trạng thái có thể thay đổi thuộc về người gọi không nhập thủ tục và các đột biến không thoát khỏi quy trình trừ các giá trị trả về! Nhưng có rất ít chương trình có thể thực hiện nhiều công việc mà không làm thay đổi trạng thái, và các lỗi luôn xuất hiện trở lại tại giao diện.
Steve

1
Thành thật mà nói, hầu hết các ngôn ngữ hiện đại cho phép sử dụng một phong cách lập trình chức năng (với một chút kỷ luật), và tất nhiên có những ngôn ngữ dành riêng cho các mẫu chức năng. Nhưng nó là một mô hình ít tính toán hiệu quả hơn và được thổi phồng quá mức như là một giải pháp cho mọi bệnh tật nhiều như định hướng đối tượng là vào những năm 90. Hầu hết các chương trình không bị ràng buộc bởi các tính toán chuyên sâu về CPU sẽ có lợi từ việc song song hóa và thường là do khó khăn trong việc suy luận, thiết kế và triển khai chương trình theo cách có thể thực hiện song song.
Steve

1
Hầu hết các chương trình liên quan đến trạng thái đột biến, làm như vậy vì chúng phải vì lý do này hay lý do khác. Và hầu hết các chương trình không phải là không chính xác vì chúng sử dụng trạng thái chia sẻ hoặc cập nhật nó một cách bất thường - thường là do chúng nhận được rác bất ngờ làm đầu vào (xác định đầu ra rác) hoặc do chúng hoạt động sai trên đầu vào của chúng (sai mục đích Để có thể đạt được <thứ gì đó). Các mẫu chức năng làm rất ít để giải quyết điều này.
Steve

1
@Steve Tôi có thể ít nhất là nửa chừng đồng ý với bạn vì tôi ở bên đó chỉ khám phá những cách để làm mọi thứ theo cách an toàn hơn từ các ngôn ngữ như C hoặc C ++, và tôi thực sự không nghĩ rằng chúng ta cần phải đi đầy đủ -blown tinh khiết chức năng để làm điều đó. Nhưng tôi thấy một số khái niệm trong FP ít nhất cũng hữu ích. Cuối cùng tôi đã viết một câu trả lời về cách tôi thấy PDS hữu ích ở đây và lợi ích lớn nhất tôi tìm thấy về PDS thực sự không phải là an toàn luồng, mà là những thứ như chỉnh sửa, chỉnh sửa không phá hủy, an toàn ngoại lệ, hoàn tác đơn giản, v.v .:

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.