2008-10-01 6 views
137

Gần đây tôi đã xem F #, và mặc dù tôi không thể nhảy qua hàng rào bất cứ lúc nào, nó chắc chắn làm nổi bật một số khu vực mà C# (hoặc hỗ trợ thư viện) có thể làm cho cuộc sống dễ dàng hơn.switch/pattern matching idea

Cụ thể, tôi đang suy nghĩ về khả năng khớp mẫu của F #, cho phép cú pháp rất phong phú - nhiều biểu cảm hơn so với chuyển đổi hiện tại/điều kiện C# tương đương có điều kiện. Tôi sẽ không cố gắng đưa ra một ví dụ trực tiếp (F # của tôi không phải là nó), nhưng trong ngắn hạn nó cho phép:

  • khớp với loại (với kiểm tra đầy đủ cho các công đoàn bị phân biệt đối xử) suy luận kiểu cho biến ràng buộc, cho phép truy cập thành viên vv]
  • trận đấu bởi vị
  • kết hợp của các bên trên (và có thể một số kịch bản khác tôi không biết)

trong khi nó sẽ là đáng yêu để C# cuối cùng mượn [ahem] một số sự phong phú này, trong thời gian đó tôi đã xem xét những gì có thể được thực hiện t runtime - ví dụ, nó là khá dễ dàng để gõ cùng một số đối tượng để cho phép:

var getRentPrice = new Switch<Vehicle, int>() 
     .Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle 
     .Case<Bicycle>(30) // returns a constant 
     .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20) 
     .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20) 
     .ElseThrow(); // or could use a Default(...) terminator 

nơi getRentPrice là một xe Func <, int >.

[note - có thể chuyển/trường hợp ở đây là các điều khoản sai ... nhưng nó cho thấy những ý tưởng]

Đối với tôi, đây là rõ ràng hơn rất nhiều so với tương đương bằng cách sử dụng lặp đi lặp lại nếu/khác, hoặc một ternary composit có điều kiện (được rất lộn xộn cho các biểu thức không tầm thường - dấu ngoặc đơn). Nó cũng tránh được khi truyền và cho phép tiện ích mở rộng đơn giản (trực tiếp hoặc thông qua các phương thức mở rộng) cho các kết quả cụ thể hơn, ví dụ: so khớp InRange (...) có thể so sánh với VB ... Trường hợp "x Để y "sử dụng.

Tôi chỉ đang cố gắng đánh giá liệu mọi người có nghĩ rằng có nhiều lợi ích từ các cấu trúc như trên (trong trường hợp không hỗ trợ ngôn ngữ) không?

Lưu ý thêm rằng tôi đã chơi với 3 biến thể của trên:

  • một Func < TSource, TValue > phiên bản để đánh giá - so sánh với báo cáo có điều kiện ternary composit
  • một hành động <TSource> phiên bản - có thể so sánh nếu/else nếu/else nếu/else if/else
  • biểu thức < Func < TSource, TValue > > phiên bản - là phiên bản đầu tiên, nhưng có thể sử dụng bởi các nhà cung cấp LINQ tùy ý

Ngoài ra, sử dụng phiên bản Expression cho phép biểu hiện lại cây biểu thị, thay vì sử dụng lặp đi lặp lại yêu cầu. Tôi đã không kiểm tra gần đây, nhưng trong một số ban đầu Entity Framework xây dựng tôi dường như nhớ lại điều này là cần thiết, vì nó không giống như InvocationExpression rất nhiều. Nó cũng cho phép sử dụng hiệu quả hơn với LINQ-to-Objects, vì nó tránh lặp lại các cuộc gọi đại biểu - các thử nghiệm cho thấy một kết quả giống như trên (sử dụng biểu thức biểu thức) thực hiện ở cùng tốc độ [nhanh hơn, thực tế] so với C# tương đương câu lệnh điều kiện tổng hợp.Để hoàn thành, Func <...> dựa trên phiên bản mất 4 lần miễn là câu lệnh điều kiện C#, nhưng vẫn còn rất nhanh và không có khả năng là một nút cổ chai lớn trong hầu hết các trường hợp sử dụng.

Tôi hoan nghênh mọi suy nghĩ/đầu vào/phê bình/v.v. ở trên (hoặc về khả năng hỗ trợ ngôn ngữ C# phong phú hơn ... đây là hy vọng ;-p).

+10

Bạn có thể sử dụng VB .NET hỗ trợ điều này trong tuyên bố trường hợp chọn. Eek! –

+1

Tôi thích ý tưởng này và nó làm cho một hình thức rất đẹp và linh hoạt hơn của một trường hợp chuyển đổi; Tuy nhiên, đây không phải là cách thực sự được tôn tạo của việc sử dụng cú pháp giống như LINQ như một trình bao bọc if-then? Tôi sẽ không khuyến khích ai đó sử dụng điều này thay cho thỏa thuận thực sự, tức là tuyên bố 'chuyển đổi trường hợp '. Đừng làm cho tôi sai, tôi nghĩ rằng nó có vị trí của nó và tôi có lẽ sẽ tìm cách để thực hiện. – IAbstract

+0

Nếu bạn viết một cái gì đó nhiều hơn một hello-world với các mẫu hoạt động (F # 3.0), tôi nghĩ, điều đó sẽ đẩy bạn qua các cạnh ... –

Trả lời

4

Tôi biết đó là một chủ đề cũ, nhưng trong C# 7 bạn có thể làm:

switch(shape) 
{ 
    case Circle c: 
     WriteLine($"circle with radius {c.Radius}"); 
     break; 
    case Rectangle s when (s.Length == s.Height): 
     WriteLine($"{s.Length} x {s.Height} square"); 
     break; 
    case Rectangle r: 
     WriteLine($"{r.Length} x {r.Height} rectangle"); 
     break; 
    default: 
     WriteLine("<unknown shape>"); 
     break; 
    case null: 
     throw new ArgumentNullException(nameof(shape)); 
} 
+0

bạn hoàn toàn đúng - các tính năng ngôn ngữ mới hoàn toàn phù hợp ở đây –

4

Mặc dù nó không phải là 'C-sharpey' để bật kiểu, tôi biết rằng cấu trúc sẽ khá hữu ích khi sử dụng chung - Tôi có ít nhất một dự án cá nhân có thể sử dụng nó (mặc dù máy ATM có thể quản lý). Có nhiều vấn đề về hiệu suất biên dịch không, với việc biểu hiện viết lại cây?

+0

Không nếu bạn lưu trữ lại đối tượng để sử dụng lại (phần lớn là cách biểu thức C# lambda hoạt động, ngoại trừ trình biên dịch ẩn mã). Việc viết lại chắc chắn cải thiện hiệu suất biên dịch - tuy nhiên, để sử dụng thường xuyên (thay vì LINQ-to-Something), tôi mong đợi phiên bản đại biểu có thể hữu ích hơn. –

+0

Cũng lưu ý - nó không nhất thiết phải là một chuyển đổi trên loại - nó cũng có thể được sử dụng như một điều kiện tổng hợp (thậm chí thông qua LINQ) - nhưng không có một x lộn xộn> Thử nghiệm? Kết quả 1: (Test2? Result2: (Test3? Kết quả 3: Result4)) –

+0

Rất vui được biết, mặc dù tôi có nghĩa là hiệu suất * biên dịch *: csc.exe mất bao lâu - Tôi không đủ quen thuộc với C# biết nếu điều đó thực sự là một vấn đề, nhưng đó là một vấn đề lớn đối với C++. –

22

Tôi không nghĩ rằng các loại thư viện này (có thể hoạt động như phần mở rộng ngôn ngữ) có thể được chấp nhận rộng rãi, nhưng chúng rất thú vị và có thể thực sự hữu ích cho các nhóm nhỏ hoạt động trong các miền cụ thể. hữu ích. Ví dụ, nếu bạn đang viết tấn của 'quy tắc kinh doanh/logic' mà không kiểm tra kiểu tùy ý như thế này và không có gì, tôi có thể thấy nó sẽ có ích như thế nào.

Tôi không biết liệu đây có phải là tính năng ngôn ngữ C# (có vẻ nghi ngờ, nhưng ai có thể nhìn thấy tương lai?).

Để tham khảo, F tương ứng # là xấp xỉ:

let getRentPrice (v : Vehicle) = 
    match v with 
    | :? Motorcycle as bike -> 100 + bike.Cylinders * 10 
    | :? Bicycle -> 30 
    | :? Car as car when car.EngineType = Diesel -> 220 + car.Doors * 20 
    | :? Car as car when car.EngineType = Gasoline -> 200 + car.Doors * 20 
    | _ -> failwith "blah" 

giả sử bạn muốn định nghĩa một hệ thống phân cấp lớp dọc theo dòng của

type Vehicle() = class end 

type Motorcycle(cyl : int) = 
    inherit Vehicle() 
    member this.Cylinders = cyl 

type Bicycle() = inherit Vehicle() 

type EngineType = Diesel | Gasoline 

type Car(engType : EngineType, doors : int) = 
    inherit Vehicle() 
    member this.EngineType = engType 
    member this.Doors = doors 
+2

Cảm ơn phiên bản F #. Tôi đoán tôi thích cách F # xử lý này, nhưng tôi không chắc chắn rằng (tổng thể) F # là sự lựa chọn đúng vào lúc này, vì vậy tôi phải đi bộ nền tảng trung ... –

25

Có thể cho rằng lý do mà C# không làm cho nó đơn giản để chuyển sang kiểu là vì nó chủ yếu là một ngôn ngữ hướng đối tượng, và cách 'chính xác' để làm điều này trong các thuật ngữ hướng đối tượng sẽ là định nghĩa phương thức GetRentPrice trên Vehicle và ghi đè nó trong các lớp dẫn xuất.

Điều đó nói rằng, tôi đã dành một chút thời gian chơi với các ngôn ngữ đa chức năng và mô hình như F # và Haskell có loại khả năng này, và tôi đã gặp một số nơi hữu ích trước đây (ví dụ: khi bạn không viết các loại bạn cần phải bật để bạn không thể thực hiện một phương thức ảo trên chúng) và đó là thứ tôi muốn chào đón vào ngôn ngữ cùng với các công đoàn phân biệt đối xử.

[Edit: phần Removed về hiệu suất như Marc chỉ ra nó có thể là ngắn mạch]

Một vấn đề tiềm năng là một khả năng sử dụng một - đó là rõ ràng từ cuộc gọi cuối cùng những gì sẽ xảy ra nếu trận đấu không đáp ứng bất kỳ điều kiện, nhưng hành vi nếu nó phù hợp với hai hoặc nhiều điều kiện là gì? Nó có nên ném một ngoại lệ không? Có nên trả lại trận đấu đầu tiên hoặc trận đấu cuối cùng không? Một cách tôi có xu hướng sử dụng để giải quyết loại vấn đề này là sử dụng một trường từ điển với kiểu là khóa và giá trị lambda làm giá trị, khá dễ sử dụng khi sử dụng cú pháp khởi tạo đối tượng; tuy nhiên, điều này chỉ giải thích cho loại bê tông và không cho phép các vị từ bổ sung có thể không phù hợp với các trường hợp phức tạp hơn. [Lưu ý phụ - nếu bạn nhìn vào đầu ra của trình biên dịch C#, nó thường chuyển đổi các câu lệnh chuyển đổi thành các bảng nhảy dựa trên từ điển, vì vậy không có lý do chính đáng nào nó không thể hỗ trợ chuyển đổi trên các loại]

+1

Thực ra - phiên bản tôi có ngắn mạch trong cả hai phiên bản đại biểu và biểu thức. Phiên bản biểu thức biên dịch thành một hợp chất có điều kiện; phiên bản đại biểu chỉ đơn giản là một tập hợp các biến vị ngữ và func/actions - một khi nó có một kết quả khớp với nó. –

+0

Thú vị - từ một cái nhìn lướt qua, tôi cho rằng nó sẽ phải thực hiện ít nhất là kiểm tra cơ bản của từng điều kiện vì nó trông giống như một chuỗi phương pháp, nhưng bây giờ tôi nhận ra rằng các phương thức thực sự là chuỗi đối tượng để xây dựng nó. Tôi sẽ chỉnh sửa câu trả lời của mình để xóa câu lệnh đó. –

82

Bart De Smet của excellent blog có một phần 8 loạt về làm chính xác những gì bạn mô tả. Tìm phần đầu tiên here.

+0

Thật không may, có vẻ như liên kết đó đã chết. Điều này tương tự như những gì đã có trong bản gốc? http://www.pirrmann.net/pattern-matching-in-c-sharp-introduction/ – Robert

+0

Nó tương tự về mục tiêu, nhưng không hoàn thành, bỏ phân biệt đối xử theo loại, bỏ lỡ ràng buộc và phân biệt đối xử trên các loại bản ghi (trừ các bộ dữ liệu) và có cú pháp giới hạn hơn. – mancaus

+4

Liên kết bị hỏng. Và đây là lý do tại sao bạn không nên đăng liên kết dưới dạng câu trả lời mà không tóm tắt nội dung của họ. – leigero

3

Tôi nghĩ rằng điều này trông thực sự thú vị (+1), nhưng một điều cần phải cẩn thận: trình biên dịch C# là khá tốt tại tối ưu hóa chuyển đổi báo cáo.Không chỉ cho ngắn mạch - bạn nhận được hoàn toàn khác nhau IL tùy thuộc vào bao nhiêu trường hợp bạn có và như vậy.

Ví dụ cụ thể của bạn làm điều tôi thấy rất hữu ích - không có cú pháp tương đương với từng trường hợp, như (ví dụ) typeof(Motorcycle) không phải là hằng số.

Điều này trở nên thú vị hơn trong ứng dụng động - logic của bạn ở đây có thể dễ dàng theo hướng dữ liệu, cho phép thực thi kiểu 'quy tắc động cơ'.

37

Sau khi cố gắng thực hiện những điều "chức năng" như vậy trong C# (và thậm chí cố gắng đọc sách), tôi đã đi đến kết luận rằng không, với một vài ngoại lệ, những thứ như vậy không giúp được gì nhiều.

Lý do chính là các ngôn ngữ như F # nhận được nhiều quyền lực từ việc hỗ trợ các tính năng này. Không phải "bạn có thể làm điều đó", nhưng "nó đơn giản, rõ ràng, nó được mong đợi".

Ví dụ, trong khớp mẫu, bạn sẽ nhận được trình biên dịch cho bạn biết nếu có một trận đấu không đầy đủ hoặc khi một trận đấu khác sẽ không bao giờ bị trúng. Điều này ít hữu ích hơn với các kiểu kết thúc mở, nhưng khi kết hợp một công đoàn hoặc bộ dữ liệu bị phân biệt, nó rất tiện lợi. Trong F #, bạn mong đợi mọi người phù hợp với mẫu và ngay lập tức có ý nghĩa.

"Vấn đề" là khi bạn bắt đầu sử dụng một số khái niệm chức năng, bạn nên tiếp tục sử dụng một số khái niệm chức năng. Tuy nhiên, tận dụng tuples, chức năng, ứng dụng phương pháp từng phần và currying, khớp mẫu, chức năng lồng nhau, generics, hỗ trợ monad, vv trong C# được rất xấu xí, rất nhanh chóng. Đó là niềm vui, và một số người rất thông minh đã làm một số điều rất thú vị trong C#, nhưng thực sự sử dụng nó cảm thấy nặng nề.

Những gì tôi đã kết thúc bằng cách sử dụng thường xuyên (qua dự án) trong C#: chức năng

  • Trình tự, thông qua phương pháp khuyến nông cho IEnumerable. Những thứ như ForEach hoặc Process ("Apply"? - thực hiện một hành động trên một mục chuỗi khi nó được liệt kê) phù hợp vì cú pháp C# hỗ trợ nó tốt.
  • Tóm tắt các mẫu tuyên bố chung. Phức tạp thử/bắt/cuối cùng khối hoặc tham gia khác (thường rất nhiều chung chung) khối mã. Mở rộng LINQ-to-SQL cũng phù hợp ở đây.
  • Tuples, ở một mức độ nào đó.

** Nhưng lưu ý: Việc thiếu khái niệm tổng quát và loại tự động thực sự cản trở việc sử dụng ngay cả các tính năng này. **

Tất cả điều này nói, như một người khác đã đề cập, trong một nhóm nhỏ, cho một mục đích cụ thể, có, có lẽ họ có thể giúp đỡ nếu bạn đang mắc kẹt với C#. Nhưng theo kinh nghiệm của tôi, họ thường cảm thấy rắc rối hơn là đáng giá - YMMV.

Một số liên kết khác:

5

IMHO cách OO làm việc như vậy là mô hình của khách. Các phương thức thành viên của khách truy cập của bạn chỉ hoạt động như các cấu trúc trường hợp và bạn cho phép chính ngôn ngữ đó xử lý công văn thích hợp mà không phải "nhìn trộm" các loại.

9

Đối sánh mẫu (như được mô tả here), mục đích của nó là phân tích giá trị theo đặc điểm loại của chúng. Tuy nhiên, khái niệm về một lớp (hoặc kiểu) trong C# không đồng ý với bạn.

Có chú ý sai với thiết kế ngôn ngữ đa mô hình, ngược lại, rất hay khi có lambdas trong C# và Haskell có thể thực hiện các công cụ bắt buộc đối với ví dụ: IO. Nhưng nó không phải là một giải pháp rất thanh lịch, không phải trong thời trang Haskell.

Nhưng vì các ngôn ngữ lập trình thủ tục tuần tự có thể được hiểu về phép tính lambda, và C# xảy ra để phù hợp với các tham số của ngôn ngữ thủ tục tuần tự, nó phù hợp. Tuy nhiên, lấy một cái gì đó từ bối cảnh chức năng thuần túy của Haskell nói, và sau đó đưa tính năng đó vào một ngôn ngữ không thuần khiết, tốt, làm điều đó, sẽ không đảm bảo một kết quả tốt hơn.

Điểm của tôi là điều này, điều làm cho mẫu phù hợp với đánh dấu được gắn với thiết kế ngôn ngữ và mô hình dữ liệu. Có nói rằng, tôi không tin rằng mô hình phù hợp để là một tính năng hữu ích của C# bởi vì nó không giải quyết các vấn đề điển hình C# cũng không phù hợp tốt trong mô hình lập trình bắt buộc.

+1

Có thể. Thật vậy, tôi sẽ đấu tranh để nghĩ về một lý thuyết "sát thủ" thuyết phục vì sao nó sẽ là ** cần thiết ** (trái với "có lẽ tốt đẹp trong một vài trường hợp cạnh với chi phí làm cho ngôn ngữ phức tạp hơn"). –

13

Để trả lời câu hỏi của bạn, vâng tôi nghĩ rằng cấu trúc cú pháp khớp mẫu là hữu ích. Tôi cho một người muốn xem hỗ trợ cú pháp trong C# cho nó.

Đây là triển khai thực hiện của tôi về một lớp cung cấp (gần) cú pháp tương tự như bạn mô tả

public class PatternMatcher<Output> 
{ 
    List<Tuple<Predicate<Object>, Func<Object, Output>>> cases = new List<Tuple<Predicate<object>,Func<object,Output>>>(); 

    public PatternMatcher() { }   

    public PatternMatcher<Output> Case(Predicate<Object> condition, Func<Object, Output> function) 
    { 
     cases.Add(new Tuple<Predicate<Object>, Func<Object, Output>>(condition, function)); 
     return this; 
    } 

    public PatternMatcher<Output> Case<T>(Predicate<T> condition, Func<T, Output> function) 
    { 
     return Case(
      o => o is T && condition((T)o), 
      o => function((T)o)); 
    } 

    public PatternMatcher<Output> Case<T>(Func<T, Output> function) 
    { 
     return Case(
      o => o is T, 
      o => function((T)o)); 
    } 

    public PatternMatcher<Output> Case<T>(Predicate<T> condition, Output o) 
    { 
     return Case(condition, x => o); 
    } 

    public PatternMatcher<Output> Case<T>(Output o) 
    { 
     return Case<T>(x => o); 
    } 

    public PatternMatcher<Output> Default(Func<Object, Output> function) 
    { 
     return Case(o => true, function); 
    } 

    public PatternMatcher<Output> Default(Output o) 
    { 
     return Default(x => o); 
    } 

    public Output Match(Object o) 
    { 
     foreach (var tuple in cases) 
      if (tuple.Item1(o)) 
       return tuple.Item2(o); 
     throw new Exception("Failed to match"); 
    } 
} 

Dưới đây là một số mã kiểm tra:

public enum EngineType 
    { 
     Diesel, 
     Gasoline 
    } 

    public class Bicycle 
    { 
     public int Cylinders; 
    } 

    public class Car 
    { 
     public EngineType EngineType; 
     public int Doors; 
    } 

    public class MotorCycle 
    { 
     public int Cylinders; 
    } 

    public void Run() 
    { 
     var getRentPrice = new PatternMatcher<int>() 
      .Case<MotorCycle>(bike => 100 + bike.Cylinders * 10) 
      .Case<Bicycle>(30) 
      .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20) 
      .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20) 
      .Default(0); 

     var vehicles = new object[] { 
      new Car { EngineType = EngineType.Diesel, Doors = 2 }, 
      new Car { EngineType = EngineType.Diesel, Doors = 4 }, 
      new Car { EngineType = EngineType.Gasoline, Doors = 3 }, 
      new Car { EngineType = EngineType.Gasoline, Doors = 5 }, 
      new Bicycle(), 
      new MotorCycle { Cylinders = 2 }, 
      new MotorCycle { Cylinders = 3 }, 
     }; 

     foreach (var v in vehicles) 
     { 
      Console.WriteLine("Vehicle of type {0} costs {1} to rent", v.GetType(), getRentPrice.Match(v)); 
     } 
    } 
0

Bạn có thể đạt được những gì bạn đang sau bởi sử dụng thư viện tôi đã viết, được gọi là OneOf

Lợi thế chính trên switch (và ifexceptions as control flow) là nó là thời gian biên dịch an toàn - không có xử lý mặc định hoặc rơi qua

OneOf<Motorcycle, Bicycle, Car> vehicle = ... //assign from one of those types 
    var getRentPrice = vehicle 
     .Match(
      bike => 100 + bike.Cylinders * 10, // "bike" here is typed as Motorcycle 
      bike => 30, // returns a constant 
      car => car.EngineType.Match(
       diesel => 220 + car.Doors * 20 
       petrol => 200 + car.Doors * 20 
      ) 
     ); 

Đó là về NuGet và mục tiêu net451 và netstandard1.6