Hệ thống loại của C # thiếu một vài tính năng cần thiết để triển khai đúng các lớp loại như một giao diện.
Hãy bắt đầu với ví dụ của bạn, nhưng khóa đang hiển thị một tài khoản đầy đủ hơn về kiểu chữ là gì và làm gì, sau đó cố gắng ánh xạ chúng tới các bit C #.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Đây là định nghĩa lớp loại, hoặc tương tự như giao diện. Bây giờ hãy xem xét một định nghĩa về một loại và đó là việc thực hiện lớp loại đó.
data Awesome a = Awesome a a
instance Functor Awesome where
fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)
Bây giờ chúng ta có thể thấy rất rõ ràng một thực tế khác biệt của các lớp loại mà bạn không thể có với các giao diện. Việc thực hiện lớp loại không phải là một phần của định nghĩa của loại. Trong C #, để triển khai một giao diện, bạn phải triển khai nó như là một phần của định nghĩa về loại thực hiện giao diện đó. Điều này có nghĩa là bạn không thể thực hiện giao diện cho loại mà bạn không tự thực hiện, tuy nhiên trong Haskell, bạn có thể triển khai lớp loại cho bất kỳ loại nào bạn có quyền truy cập.
Đó có thể là lớn nhất ngay lập tức, nhưng có một sự khác biệt khá quan trọng khác khiến cho tương đương C # thực sự không hoạt động tốt và bạn đang chạm vào nó trong câu hỏi của bạn. Đó là về đa hình. Ngoài ra, có một số điều tương đối chung Haskell cho phép bạn thực hiện với các lớp loại hoàn toàn không dịch đặc biệt là khi bạn bắt đầu xem xét số lượng chung trong các loại tồn tại hoặc các phần mở rộng GHC khác như ADT chung.
Bạn thấy đấy, với Haskell, bạn có thể xác định hàm functor
data List a = List a (List a) | Terminal
data Tree a = Tree val (Tree a) (Tree a) | Terminal
instance Functor List where
fmap :: (a -> b) -> List a -> List b
fmap f (List a Terminal) = List (f a) Terminal
fmap f (List a rest) = List (f a) (fmap f rest)
instance Functor Tree where
fmap :: (a -> b) -> Tree a -> Tree b
fmap f (Tree val Terminal Terminal) = Tree (f val) Terminal Terminal
fmap f (Tree val Terminal right) = Tree (f val) Terminal (fmap f right)
fmap f (Tree val left Terminal) = Tree (f val) (fmap f left) Terminal
fmap f (Tree val left right) = Tree (f val) (fmap f left) (fmap f right)
Sau đó, trong tiêu dùng bạn có thể có một chức năng:
mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar
Đây là vấn đề. Trong C # làm thế nào để bạn viết chức năng này?
public Tree<a> : Functor<a>
{
public a Val { get; set; }
public Tree<a> Left { get; set; }
public Tree<a> Right { get; set; }
public Functor<b> fmap<b>(Func<a,b> f)
{
return new Tree<b>
{
Val = f(val),
Left = Left.fmap(f);
Right = Right.fmap(f);
};
}
}
public string Show<a>(Showwable<a> ror)
{
return ror.Show();
}
public Functor<String> mapsSomething<a,b>(Functor<a> rar) where a : Showwable<b>
{
return rar.fmap(Show<b>);
}
Vì vậy, có một vài điều xảy ra với phiên bản C #, cho một điều tôi thậm chí không chắc chắn nó sẽ cho phép bạn sử dụng các <b>
vòng loại như tôi đã làm ở đó, nhưng mà không có nó tôi là chắc chắn nó sẽ không cử Show<>
một cách thích hợp (bạn có thể thử và biên dịch để tìm hiểu; tôi đã không).
Tuy nhiên, vấn đề lớn hơn ở đây là không giống như ở Haskell, nơi chúng tôi đã Terminal
xác định là một phần của loại và sau đó có thể sử dụng thay thế cho loại này, do C # thiếu tính đa hình tham số thích hợp (trở nên siêu rõ ràng ngay khi bạn cố gắng xen vào F # với C #) bạn không thể phân biệt rõ ràng hay rõ ràng Phải hay Trái là Terminal
s. Điều tốt nhất bạn có thể làm là sử dụng null
, nhưng nếu bạn đang cố gắng tạo một loại giá trị a Functor
hoặc trong trường hợp Either
bạn phân biệt hai loại mà cả hai đều mang một giá trị thì sao? Bây giờ bạn phải sử dụng một loại và có hai giá trị khác nhau để kiểm tra và chuyển đổi giữa để mô hình hóa sự phân biệt đối xử của bạn?
Việc thiếu các kiểu tổng hợp, loại kết hợp, ADT, bất cứ điều gì bạn muốn gọi chúng thực sự làm cho rất nhiều kiểu chữ sẽ khiến bạn bỏ đi vì vào cuối ngày, chúng cho phép bạn coi nhiều loại (hàm tạo) là một loại duy nhất, và hệ thống kiểu cơ bản của .NET đơn giản là không có khái niệm như vậy.