Khi lập trình theo kiểu Chức năng, bạn có trạng thái ứng dụng duy nhất mà bạn dệt thông qua logic ứng dụng không?


12

Làm cách nào để xây dựng một hệ thống có tất cả những điều sau đây :

  1. Sử dụng các hàm thuần túy với các đối tượng bất biến.
  2. Chỉ truyền vào dữ liệu hàm mà hàm cần, không còn nữa (tức là không có đối tượng trạng thái ứng dụng lớn)
  3. Tránh có quá nhiều đối số cho các chức năng.
  4. Tránh phải xây dựng các đối tượng mới chỉ nhằm mục đích đóng gói và giải nén các tham số cho các chức năng, đơn giản là để tránh quá nhiều tham số được truyền cho các chức năng. Nếu tôi sẽ đóng gói nhiều mục vào một hàm dưới dạng một đối tượng, tôi muốn đối tượng đó là chủ sở hữu của dữ liệu đó, không phải là thứ được xây dựng tạm thời

Dường như với tôi rằng đơn vị Nhà nước phá vỡ quy tắc số 2, mặc dù điều đó không rõ ràng bởi vì nó được dệt qua đơn vị.

Tôi có cảm giác tôi cần sử dụng Lenses bằng cách nào đó, nhưng rất ít được viết về nó cho các ngôn ngữ phi chức năng.

Lý lịch

Như một bài tập, tôi đang chuyển đổi một trong những ứng dụng hiện có của mình từ kiểu hướng đối tượng sang kiểu chức năng. Điều đầu tiên tôi đang cố gắng làm là tạo ra càng nhiều lõi bên trong của ứng dụng càng tốt.

Một điều tôi đã nghe là làm thế nào để quản lý "Trạng thái" bằng ngôn ngữ thuần chức năng, và đây là điều tôi tin là được thực hiện bởi các đơn vị Nhà nước, đó là về mặt logic, bạn gọi là một hàm thuần túy, "chuyển qua trạng thái của thế giới như nó là ", sau đó khi hàm trả về, nó sẽ trả về cho bạn trạng thái của thế giới khi nó thay đổi.

Để minh họa, cách bạn có thể thực hiện "thế giới xin chào" theo cách hoàn toàn có chức năng giống như, bạn chuyển qua chương trình trạng thái của màn hình và nhận lại trạng thái của màn hình với "thế giới xin chào" được in trên đó. Vì vậy, về mặt kỹ thuật, bạn đang thực hiện một cuộc gọi đến một chức năng thuần túy và không có tác dụng phụ.

Dựa vào đó, tôi đã duyệt qua ứng dụng của mình và: 1. Đầu tiên đặt tất cả trạng thái ứng dụng của tôi vào một đối tượng toàn cầu duy nhất (GameState) 2. Thứ hai, tôi đã biến GameState thành bất biến. Bạn không thể thay đổi nó. Nếu bạn cần một sự thay đổi, bạn phải xây dựng một cái mới. Tôi đã làm điều này bằng cách thêm một hàm tạo sao chép, tùy ý lấy một hoặc nhiều trường đã thay đổi. 3. Với mỗi ứng dụng, tôi chuyển vào GameState dưới dạng tham số. Trong chức năng, sau khi thực hiện những gì nó sẽ làm, nó tạo ra một GameState mới và trả về nó.

Làm cách nào tôi có lõi chức năng thuần túy và một vòng lặp ở bên ngoài cung cấp GameState đó vào vòng lặp công việc chính của ứng dụng.

Câu hỏi của tôi:

Bây giờ, vấn đề của tôi là, GameState có khoảng 15 đối tượng bất biến khác nhau. Nhiều chức năng ở mức thấp nhất chỉ hoạt động trên một vài trong số các đối tượng đó, chẳng hạn như giữ điểm. Vì vậy, giả sử tôi có một hàm tính điểm. Ngày nay, GameState được chuyển đến chức năng này, điều chỉnh điểm số bằng cách tạo GameState mới với điểm số mới.

Một cái gì đó về điều đó có vẻ sai. Hàm không cần toàn bộ GameState. Nó chỉ cần đối tượng Điểm. Vì vậy, tôi đã cập nhật nó để vượt qua Điểm và chỉ trả về Điểm.

Điều đó dường như có ý nghĩa, vì vậy tôi đã đi xa hơn với các chức năng khác. Một số chức năng sẽ yêu cầu tôi chuyển 2, 3 hoặc 4 tham số từ GameState, nhưng khi tôi sử dụng mô hình cho đến tận lõi ngoài của ứng dụng, tôi sẽ chuyển qua trạng thái ứng dụng ngày càng nhiều. Giống như, ở đầu vòng lặp công việc, tôi sẽ gọi một phương thức, sẽ gọi phương thức đó sẽ gọi một phương thức, v.v., tất cả các cách để tính điểm. Điều đó có nghĩa là điểm số hiện tại được chuyển qua tất cả các lớp đó chỉ vì một hàm ở dưới cùng sẽ tính điểm.

Vì vậy, bây giờ tôi có chức năng với đôi khi hàng chục tham số. Tôi có thể đặt các tham số đó vào một đối tượng để giảm số lượng tham số, nhưng sau đó tôi muốn lớp đó là vị trí chính của trạng thái ứng dụng trạng thái, thay vì một đối tượng được xây dựng đơn giản tại thời điểm cuộc gọi đơn giản để tránh vượt qua trong nhiều tham số, và sau đó giải nén chúng.

Vì vậy, bây giờ tôi tự hỏi nếu vấn đề tôi có là các chức năng của tôi được lồng quá sâu. Đây là kết quả của việc muốn có các hàm nhỏ, vì vậy tôi tái cấu trúc khi một hàm trở nên lớn và chia nó thành nhiều hàm nhỏ hơn. Nhưng làm điều đó tạo ra một hệ thống phân cấp sâu hơn và bất cứ thứ gì được truyền vào các hàm bên trong cần phải được truyền vào hàm bên ngoài ngay cả khi hàm ngoài không hoạt động trực tiếp trên các đối tượng đó.

Có vẻ như chỉ cần vượt qua trong GameState trên đường đi đã tránh được vấn đề này. Nhưng tôi trở lại vấn đề ban đầu là chuyển nhiều thông tin đến một chức năng hơn chức năng cần.


1
Tôi không phải là chuyên gia về thiết kế và đặc biệt là chức năng, nhưng vì trò chơi của bạn có bản chất phát triển, bạn có chắc rằng lập trình chức năng là một mô hình phù hợp với tất cả các lớp của ứng dụng của bạn không?
Walfrat

Walfrat, tôi nghĩ rằng nếu bạn nói chuyện với các chuyên gia lập trình chức năng, có lẽ bạn sẽ thấy rằng họ sẽ nói rằng mô hình lập trình chức năng có các giải pháp để quản lý trạng thái phát triển.
Daisha Lynn

Câu hỏi của bạn dường như rộng hơn đối với tôi mà chỉ nêu. Nếu chỉ về việc quản lý các trạng thái ở đây là một sự khởi đầu: hãy xem câu trả lời và liên kết trong stackoverflow.com/questions/1020653/
Kẻ

2
@DaishaLynn Tôi không nghĩ bạn nên xóa câu hỏi. Nó đã được nâng cấp và không ai cố gắng đóng nó, vì vậy tôi không nghĩ rằng nó nằm ngoài phạm vi của trang web này. Việc thiếu một câu trả lời cho đến nay có thể chỉ vì nó đòi hỏi một số chuyên môn tương đối thích hợp. Nhưng điều đó không có nghĩa là nó sẽ không được tìm thấy và trả lời cuối cùng.
Ben Aaronson

2
Quản lý trạng thái đột biến trong một chương trình chức năng thuần túy phức tạp mà không có sự trợ giúp ngôn ngữ đáng kể là một nỗi đau rất lớn. Trong Haskell, nó có thể quản lý được do các đơn vị, cú pháp ngắn gọn, suy luận kiểu rất tốt nhưng nó vẫn có thể rất khó chịu. Trong C # tôi nghĩ bạn sẽ gặp nhiều rắc rối hơn.
Phục hồi lại

Câu trả lời:


2

Tôi không chắc có một giải pháp tốt. Điều này có thể hoặc không phải là một câu trả lời, nhưng nó quá dài cho một nhận xét. Tôi đã làm một điều tương tự và các thủ thuật sau đây đã giúp:

  • Phân chia GameStatethứ bậc, để bạn có được 3-5 phần nhỏ hơn thay vì 15.
  • Hãy để nó thực hiện các giao diện, vì vậy các phương thức của bạn chỉ nhìn thấy các phần cần thiết. Đừng bao giờ bỏ chúng lại khi bạn nói dối bản thân về loại thực.
  • Hãy để các bộ phận thực hiện giao diện, để bạn có thể kiểm soát tốt những gì bạn vượt qua.
  • Sử dụng các đối tượng tham số, nhưng thực hiện một cách tiết kiệm và cố gắng biến chúng thành các đối tượng thực với hành vi của riêng chúng.
  • Đôi khi vượt qua một chút nhiều hơn mức cần thiết là tốt hơn một danh sách tham số dài.

Vì vậy, bây giờ tôi tự hỏi nếu vấn đề tôi có là các chức năng của tôi được lồng quá sâu.

Tôi không nghĩ vậy. Tái cấu trúc thành các hàm nhỏ là đúng, nhưng có lẽ bạn có thể tập hợp lại chúng tốt hơn. Đôi khi, điều đó là không thể, đôi khi nó chỉ cần một cái nhìn thứ hai (hoặc thứ ba) về vấn đề.

So sánh thiết kế của bạn với một đột biến. Có những điều đã trở nên tồi tệ hơn bằng cách viết lại? Nếu vậy, bạn không thể làm cho chúng tốt hơn giống như cách bạn đã làm ban đầu sao?


Có người bảo tôi thay đổi thiết kế của mình để chức năng chỉ mất một tham số để tôi có thể sử dụng currying. Tôi đã thử một hàm đó, vì vậy thay vì gọi DeleteEntity (a, b, c), bây giờ tôi gọi DeleteEntity (a) (b) (c). Thật dễ thương, và nó được cho là làm cho nó trở nên dễ kết hợp hơn, nhưng tôi vẫn chưa nhận được nó.
Daisha Lynn

@DaishaLynn Tôi đang sử dụng Java và không có đường cú pháp ngọt ngào cho món cà ri, vì vậy (đối với tôi) nó không đáng để thử. Tôi khá hoài nghi về việc sử dụng các hàm bậc cao hơn trong trường hợp của chúng tôi, nhưng hãy cho tôi biết nếu nó hoạt động cho bạn.
maaartinus

2

Tôi không thể nói chuyện với C #, nhưng ở Haskell, cuối cùng bạn sẽ vượt qua toàn bộ bang. Bạn có thể làm điều này một cách rõ ràng hoặc với một đơn nguyên Nhà nước. Một điều bạn có thể làm để giải quyết vấn đề về chức năng nhận thêm thông tin sau đó họ cần là sử dụng Có kiểu chữ. (Nếu bạn không quen thuộc, các kiểu chữ Haskell hơi giống giao diện C #.) Với mỗi phần tử E của trạng thái, bạn có thể xác định một kiểu chữ HasE yêu cầu hàm getE trả về giá trị của E. làm một ví dụ của tất cả các kiểu chữ này. Sau đó, trong các chức năng thực tế của bạn, thay vì yêu cầu rõ ràng đơn nguyên Bang của bạn, bạn yêu cầu bất kỳ đơn vị nào thuộc về kiểu chữ Có cho các yếu tố bạn cần; hạn chế những gì chức năng có thể làm với đơn nguyên mà nó đang sử dụng. Để biết thêm thông tin về phương pháp này, xem Michael Snoyman'sđăng trên mẫu thiết kế ReaderT .

Bạn có thể có thể sao chép một cái gì đó như thế này trong C #, tùy thuộc vào cách bạn xác định trạng thái được truyền qua. Nếu bạn có một cái gì đó như

public class MyState
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
}

bạn có thể định nghĩa các giao diện IHasMyIntIHasMyStringvới các phương thức GetMyIntGetMyStringtương ứng. Các lớp nhà nước sau đó trông giống như:

public class MyState : IHasMyInt, IHasMyString
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
    public double MyDouble {get; set; }

    public int GetMyInt () 
    {
        return MyInt;
    }

    public string GetMyString ()
    {
        return MyString;
    }

    public double GetMyDouble ()
    {
        return MyDouble;
    }
}

sau đó các phương thức của bạn có thể yêu cầu IHasMyInt, IHasMyString hoặc toàn bộ MyState nếu phù hợp.

Sau đó, bạn có thể sử dụng ràng buộc where trong định nghĩa hàm để bạn có thể truyền đối tượng trạng thái, nhưng nó chỉ có thể nhận được chuỗi và int, chứ không phải gấp đôi.

public static T DoSomething<T>(T state) where T : IHasMyString, IHasMyInt
{
    var s = state.GetMyString();
    var i = state.GetMyInt();
    return state;
}

Nó thật thú vị. Vì vậy, hiện tại, nơi tôi chuyển một hàm gọi và truyền 10 tham số theo giá trị, tôi chuyển vào "gameSt'ate" 10 lần, nhưng đến 10 loại tham số khác nhau, chẳng hạn như "IHasGameScore", "IHasGameBoard", v.v. là một cách để vượt qua một tham số duy nhất mà hàm có thể chỉ ra phải thực hiện tất cả các giao diện trong một loại đó. Tôi tự hỏi nếu tôi có thể làm điều đó với một "ràng buộc chung" .. Hãy để tôi thử điều đó.
Daisha Lynn

1
Nó đã làm việc. Ở đây nó đang hoạt động: dotnetfiddle.net/cfmDbs .
Daisha Lynn

1

Tôi nghĩ bạn sẽ làm tốt khi tìm hiểu về Redux hoặc Elm và cách họ xử lý câu hỏi này.

Về cơ bản, bạn có một hàm thuần túy nhận toàn bộ trạng thái và hành động mà người dùng thực hiện và trả về trạng thái mới.

Hàm đó sau đó gọi các hàm thuần túy khác, mỗi hàm xử lý một phần cụ thể của trạng thái. Tùy thuộc vào hành động, nhiều chức năng trong số này có thể không làm gì ngoài việc trả lại trạng thái ban đầu không thay đổi.

Để tìm hiểu thêm, Google Architecture Elm hoặc Redux.js.org.


Tôi không biết Elm, nhưng tôi tin rằng nó tương tự như Redux. Trong Redux, không phải tất cả các bộ giảm được gọi cho mỗi thay đổi trạng thái? Âm thanh cực kỳ kém hiệu quả.
Daisha Lynn

Khi nói đến tối ưu hóa ở mức độ thấp, đừng giả sử, đo lường. Trong thực tế, nó là rất nhiều đủ nhanh.
Daniel T.

Cảm ơn Daniel, nhưng nó sẽ không làm việc cho tôi. Tôi đã thực hiện đủ phát triển để biết rằng nó không thông báo cho mọi thành phần trong giao diện người dùng bất cứ khi nào có bất kỳ thay đổi dữ liệu nào, bất kể điều khiển có quan tâm đến điều khiển hay không.
Daisha Lynn

-2

Tôi nghĩ rằng những gì bạn đang cố gắng làm là sử dụng một ngôn ngữ hướng đối tượng theo cách không nên sử dụng, như thể đó là một ngôn ngữ chức năng thuần túy. Nó không giống như ngôn ngữ OO là tất cả xấu xa. Có những ưu điểm của một trong hai cách tiếp cận, vì vậy, tại sao bây giờ chúng ta có thể kết hợp kiểu OO với kiểu chức năng và có cơ hội để tạo ra một số đoạn mã có chức năng trong khi các đối tượng khác vẫn được định hướng để chúng ta có thể tận dụng tất cả khả năng sử dụng lại, kế thừa hoặc đa nguyên. May mắn thay, chúng tôi không còn bị ràng buộc với một trong hai cách tiếp cận, vậy tại sao bạn lại cố gắng giới hạn bản thân với một trong số họ?

Trả lời câu hỏi của bạn: không, tôi không dệt bất kỳ trạng thái cụ thể nào thông qua logic ứng dụng mà sử dụng những gì phù hợp với trường hợp sử dụng hiện tại và áp dụng các kỹ thuật có sẵn theo cách thích hợp nhất.

C # chưa sẵn sàng (chưa) được sử dụng như chức năng như bạn muốn.


3
Không hồi hộp với câu trả lời này, cũng không phải giai điệu. Tôi không lạm dụng bất cứ điều gì. Tôi đang đẩy các giới hạn của C # để sử dụng nó như một ngôn ngữ Chức năng hơn. Nó không phải là một điều hiếm gặp. Bạn dường như trái ngược về mặt triết học với nó, điều đó tốt, nhưng trong trường hợp đó, đừng nhìn vào câu hỏi này. Nhận xét của bạn là không sử dụng cho bất cứ ai. Tiến lên.
Daisha Lynn

@DaishaLynn bạn đã sai, tôi không phản đối bất kỳ cách nào và thực tế tôi sử dụng nó rất nhiều ... nhưng ở đó nó tự nhiên và có thể và không cố biến ngôn ngữ OO thành ngôn ngữ chức năng chỉ vì nó là hông làm như vậy. Bạn không cần phải đồng ý với câu trả lời của tôi nhưng điều đó không thay đổi sự thật rằng bạn không sử dụng đúng công cụ của mình.
t3chb0t

Tôi không làm điều đó bởi vì đó là hông để làm như vậy. Bản thân C # đang hướng tới việc sử dụng một phong cách chức năng. Bản thân Anders Hejlsberg đã chỉ ra như vậy. Tôi hiểu rằng bạn chỉ quan tâm đến việc sử dụng ngôn ngữ chính và tôi hiểu tại sao và khi nào thì phù hợp. Tôi chỉ không biết tại sao một người như bạn thậm chí còn ở trên chủ đề này .. Làm thế nào để bạn thực sự giúp đỡ?
Daisha Lynn

@DaishaLynn nếu bạn không thể đối phó với câu trả lời chỉ trích câu hỏi hoặc cách tiếp cận của bạn, có lẽ bạn không nên hỏi câu hỏi ở đây hoặc lần sau bạn chỉ nên thêm từ chối trách nhiệm rằng bạn chỉ quan tâm đến câu trả lời hỗ trợ ý tưởng của bạn 100% vì bạn không muốn nghe sự thật nhưng thay vì nhận được ý kiến ​​ủng hộ.
t3chb0t

Xin hãy thân mật hơn một chút với nhau. Có thể đưa ra lời phê bình mà không chê bai ngôn ngữ. Cố gắng lập trình C # theo kiểu chức năng chắc chắn không phải là "lạm dụng" hay trường hợp cạnh. Đây là một kỹ thuật phổ biến được nhiều nhà phát triển C # sử dụng để học từ các ngôn ngữ khác.
bảo vệ zumalififard
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.