Tôi đã theo dõi this F# ROP article và quyết định thử tái sản xuất trong C#, chủ yếu để xem liệu tôi có thể. Xin lỗi vì chiều dài của câu hỏi này, nhưng nếu bạn quen thuộc với ROP, nó sẽ rất dễ làm theo.Lập trình hướng tuyến đường trong C# - Làm cách nào để viết chức năng chuyển đổi?
Ông bắt đầu với một F # phân biệt đối xử công đoàn ...
type Result<'TSuccess, 'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
... mà tôi dịch sang một lớp RopValue trừu tượng, và hai triển khai cụ thể (lưu ý rằng tôi đã thay đổi tên lớp cho những người thân mà Tôi hiểu rõ hơn) ...
public abstract class RopValue<TSuccess, TFailure> {
public static RopValue<TSuccess, TFailure> Create<TSuccess, TFailure>(TSuccess input) {
return new Success<TSuccess, TFailure>(input);
}
}
public class Success<TSuccess, TFailure> : RopValue<TSuccess, TFailure> {
public Success(TSuccess value) {
Value = value;
}
public TSuccess Value { get; set; }
}
public class Failure<TSuccess, TFailure> : RopValue<TSuccess, TFailure> {
public Failure(TFailure value) {
Value = value;
}
public TFailure Value { get; set; }
}
Tôi đã thêm phương pháp tạo tĩnh để cho phép bạn tạo RopValue từ đối tượng TSuccess, được đưa vào đầu tiên của hàm xác thực.
Sau đó, tôi đã viết về chức năng liên kết. Phiên bản F # như sau ...
let bind switchFunction twoTrackInput =
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
... đó là một doddle để đọc so với C# tương đương! Tôi không biết nếu có một cách đơn giản hơn để viết những dòng này, nhưng đây là những gì tôi đã đưa ra ...
public static RopValue<TSuccess, TFailure> Bind<TSuccess, TFailure>(this RopValue<TSuccess, TFailure> input, Func<RopValue<TSuccess, TFailure>, RopValue<TSuccess, TFailure>> switchFunction) {
if (input is Success<TSuccess, TFailure>) {
return switchFunction(input);
}
return input;
}
Lưu ý rằng tôi đã viết này như là một chức năng mở rộng, vì điều đó cho phép tôi sử dụng nó một cách chức năng hơn.
Lấy trường hợp sử dụng của ông xác nhận một người, sau đó tôi đã viết một lớp người ...
public class Person {
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
... và viết chức năng xác nhận đầu tiên của tôi ...
public static RopValue<Person, string> CheckName(RopValue<Person, string> res) {
if (res.IsSuccess()) {
Person person = ((Success<Person, string>)res).Value;
if (string.IsNullOrWhiteSpace(person.Name)) {
return new Failure<Person, string>("No name");
}
return res;
}
return res;
}
Với một một vài xác thực tương tự cho email và tuổi, tôi có thể viết chức năng xác nhận tổng thể như sau ...
private static RopValue<Person, string> Validate(Person person) {
return RopValue<Person, string>
.Create<Person, string>(person)
.Bind(CheckName)
.Bind(CheckEmail)
.Bind(CheckAge);
}
Điều này làm việc tốt, và cho phép tôi làm một cái gì đó như thế này ...
Person jim = new Person {Name = "Jim", Email = "", Age = 16};
RopValue<Person, string> jimChk = Validate(jim);
Debug.WriteLine("Jim returned: " + (jimChk.IsSuccess() ? "Success" : "Failure"));
Tuy nhiên, tôi có một vài vấn đề với cách tôi đã làm điều này. Trước hết là các chức năng xác nhận yêu cầu bạn phải vượt qua trong RopValue, kiểm tra nó để thành công hay thất bại, nếu thành công, kéo ra khỏi Person và sau đó xác nhận nó. Nếu thất bại, chỉ cần trả lại.
Ngược lại, chức năng xác nhận của ông mất (tương đương) một người, và trả lại (một quả, đó là tương đương) một RopValue ...
let validateNameNotBlank person =
if person.Name = "" then Failure "Name must not be blank"
else Success person
Đây là đơn giản hơn nhiều, nhưng tôi không thể tìm ra cách thực hiện điều này trong C#.
Một vấn đề khác là chúng tôi bắt đầu chuỗi xác nhận với thành công <>, vì vậy hàm xác thực đầu tiên sẽ luôn trả về thứ gì đó từ khối "if", hoặc thất bại <> nếu xác thực không thành công hoặc thành công <> nếu chúng tôi vượt qua kiểm tra. Nếu một hàm trả về Failure <>, thì hàm tiếp theo trong chuỗi xác nhận không bao giờ được gọi, vì vậy nó chỉ ra rằng chúng ta biết rằng các phương thức này không bao giờ có thể được thông qua một Failure <>.Vì vậy, dòng cuối cùng của mỗi chức năng này không bao giờ có thể đạt được (khác với trong trường hợp kỳ lạ mà bạn tự tạo ra một thất bại <> và thông qua nó trong lúc bắt đầu, nhưng đó sẽ là vô nghĩa).
Sau đó, ông đã tạo toán tử chuyển đổi (> =>) để kết nối các chức năng xác thực. Tôi đã thử làm điều này, nhưng không thể làm cho nó hoạt động được. Để chuỗi các cuộc gọi liên tiếp đến hàm, có vẻ như tôi phải có một phương thức mở rộng trên một Func <>, mà tôi không nghĩ rằng bạn có thể làm. Tôi đã đạt được điều này ...
public static RopValue<TSuccess, TFailure> Switch<TSuccess, TFailure>(this Func<TSuccess, RopValue<TSuccess, TFailure>> switch1, Func<TSuccess, RopValue<TSuccess, TFailure>> switch2, TSuccess input) {
RopValue<TSuccess, TFailure> res1 = switch1(input);
if (res1.IsSuccess()) {
return switch2(((Success<TSuccess, TFailure>)res1).Value);
}
return new Failure<TSuccess, TFailure>(((Failure<TSuccess, TFailure>)res1).Value);
}
... nhưng không thể tìm cách sử dụng.
Vì vậy, bất kỳ ai cũng có thể giải thích cách tôi sẽ viết hàm Bind để có thể lấy một Người và trả lại RopValue (giống như của anh ta) không? Ngoài ra làm cách nào để viết chức năng chuyển đổi cho phép tôi kết nối các chức năng xác thực đơn giản?
Bất kỳ nhận xét nào khác về mã của tôi đều được chào đón. Tôi không chắc nó ở đâu gần như gọn gàng và đơn giản như nó có thể được.
Tất cả điều này đánh tôi giống như phần 'Lỗi' đơn nguyên. Bạn có cảm thấy đó là nhưng với loại 'TFailure' có hiệu quả tương đương với' Ngoại lệ '? –
Enigmativity
@Enigmativity Để trích dẫn Mark Twain, "Tôi rất vui khi có thể trả lời nhanh, tôi nói tôi không biết!" - Tôi là lập trình viên C#, học lập trình F # và lập trình hàm cùng một lúc. Tôi đã nhìn thấy các monads được thảo luận, nhưng vẫn chưa biết được chúng là gì. –
Loại 'RopValue' của bạn là một đơn nguyên. Về cơ bản monads cung cấp cho các loại bình thường siêu quyền hạn. – Enigmativity