Можно ли имитировать функциональность классов типов Haskell с помощью шаблонов C ++ (или C #)?
Я недостаточно разбираюсь в шаблонах C ++, чтобы ответить на этот вопрос. Тем не менее, я могу немного поговорить об общих типах C #.
Короткий ответ - нет. Система классов «более высокого» типа в Haskell более мощная, чем система универсальных типов в C #.
Краткое обсуждение того, что мы подразумеваем под «высшими типами», может быть полезно здесь для всех читателей, которые все еще читают это, но не знакомы с Haskell. В C # это можно сделать:
interface IEnumerable<T> { ... }
«Универсальный» тип «IEnumerable одного параметра типа» на самом деле не является «типом» сам по себе, это шаблон, из которого вы можете создавать бесконечно много новых типов, заменяя аргументы типа («int») на параметры типа (« Т "). В этом смысле он «выше» нормального типа.
Вы можете наложить ограничения на параметры типов универсальных типов:
class C<T> where T : IEnumerable<int>
Универсальный тип C может быть сконструирован с любым аргументом типа, если аргумент типа является типом, который неявно преобразуется через преобразование ссылки или упаковки в IEnumerable<int>
.
Но система типов Haskell идет еще дальше. Он поддерживает «классы типов», где ограничения, которые вы можете наложить на T, - это такие вещи, как «T имеет оператор равенства, определенный на нем». В C # операторы определены как статические методы, и нет аналога интерфейса для статических методов. В C # нет возможности обобщать многие типы на основе произвольных статических методов.
Примером, обычно приводимым для Haskell, является шаблон «монада». В нотации C # предположим, что у нас есть тип:
class MyMonad<T>
{
public static MyMonad<TOut> Bind<TIn, TOut>(MyMonad<TIn>, Func<TIn, MyMonad<TOut>>) { ... }
public MyMonad(T t) { ... }
}
Монада - это просто образец; монадический тип - это любой универсальный тип, у которого есть статический универсальный метод Bind и конструктор, соответствующий приведенному выше шаблону. В Haskell вы можете использовать более высокие типы для описания этого шаблона; в C # в системе типов нет средств для обобщения таких вещей, как статические методы и конструкторы.
Или вы могли бы сказать, что было бы более идиоматично использовать связыватель экземпляра:
class MyMonad<T>
{
public MyMonad<TOut> Bind<TOut>(MyMonad<T>, Func<T, MyMonad<TOut>>) { ... }
public MyMonad(T t) { ... }
}
Это помогает? Нет. Даже если оставить в стороне проблему с конструктором, мы не сможем придумать интерфейс, который фиксирует этот шаблон. Мы можем попробовать:
interface IMonad<T>
{
public IMonad<TOut> Bind<TOut>(IMonad<T>, Func<T, IMonad<TOut>>);
}
Но это неправильно. Это говорит о том, что монада - это что-то, что принимает монаду и функцию, которая возвращает монаду, и возвращает монаду. Это означает, что у вас может быть две реализации IMonad<T>
, скажем Maybe<T>
и Sequence<T>
, а затем иметь связыватель, который принимает последовательность и возвращает "возможно"! В этом нет никакого смысла; паттерн, который мы хотим запечатлеть, это
highertype Monad makes a pattern with TheImplementingType<T> like
{
public TheImplementingType<TOut> Bind<TOut>(TheImplementingType<T>, Func<T, TheImplementingType<TOut>>);
}
но у нас нет способа выразить это на C #.
Давайте рассмотрим ваш пример Functor. В C # у нас может быть тип
class List<T>
{
public static List<TOut> Map<TIn, TOut>(Func<TIn, TOut> mapper, List<TIn> list)
{ ... }
Или, возможно, более идиоматично, метод экземпляра:
class List<T>
{
public List<TOut> Map<TOut>(Func<T, TOut> mapper)
{ ... }
Или, что еще более идиоматично, у нас может быть статический метод как метод расширения. (Фактически, этот метод существует в библиотеке операторов последовательности в C #; его можно создать, составив «Select» на IEnumerable<T>
с «ToList»).
Что бы ни. Неважно. Дело в том, что в вашем коде на Haskell:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Вы можете сказать, что «любой универсальный тип, который предоставляет операцию сопоставления, которая соответствует вышеприведенному шаблону, называется« Functor »», а затем вы можете создавать методы, которые принимают Functors. У нас нет никакого способа обобщить «все типы, обеспечивающие операции сопоставления» на уровне пользователя в C #.
Чтобы обойти это ограничение системы типов, мы выбрали несколько наиболее мощных высших типов и встроили их прямо в язык. Сам язык распознает более высокие типы, такие как шаблон последовательности (при обработке цикла foreach), шаблон обобщенной монады (в понимании запросов; «SelectMany» - это «привязка» к произвольной монаде), шаблон продолжения (в «ожидании», входящий в C # 5), монада «Может быть» (в типах значений, допускающих значение NULL) и так далее.
Итак, чтобы решить вашу конкретную проблему, да, нет, идея создания проекции не фиксируется в системе типов, но фиксируется в языке < / em> с пониманием запросов LINQ. Если вы скажете
from x in y select z
тогда будет работать любое выражение y типа, у которого есть метод Select, который принимает y и отображение от x к z. Этот шаблон встроен в язык C #. Но если вы хотите описать какой-нибудь другой «высший» образец, вам не повезло.
Было бы неплохо иметь возможность в системе типов описывать более высокие типы, но более вероятно, что мы сохраним систему типов как есть и при необходимости запечем больше шаблонов в языке.
В этом вопросе описывается место, где я обычно вижу попытку эмуляции типов высшего порядка в C #:
Почему это общее ограничение компилируется, если кажется, что оно имеет циклическую ссылку
Идея здесь в том, что разработчик хочет разработать такой тип «Animal», чтобы:
abstract class Animal
{
public abstract void MakeFriends<T>(T newFriend)
where T : THISTYPE;
}
Вымышленный «where T: THISTYPE» пытается донести идею о том, что кошка может подружиться только с другой кошкой, собака может подружиться только с другой собакой и так далее. (Игнорируйте на данный момент тот факт, что такой шаблон, который подразумевает, что MakeFriends имеет виртуальную ковариацию для формальных типов параметров, не будет типобезопасным и, вероятно, тем самым нарушит принцип подстановки Лискова.) Эта концепция выражается в более высоких типах, но не в система типов C #. Иногда люди используют такой паттерн:
abstract class Animal<T> where T : Animal<T>
{
public abstract void MakeFriends(T newFriend);
}
class Cat : Animal<Cat>
{
public override void MakeFriends(Cat newFriend){ ... }
}
Однако на самом деле это не может обеспечить желаемое ограничение, потому что, конечно, ничто не мешает вам сказать:
class Dog : Animal<Cat>
{
public override void MakeFriends(Cat newFriend){ ... }
}
И теперь Собака может дружить с Кошкой, нарушая замысел автора «Зверя». Система типов C # просто недостаточно мощна, чтобы представлять все виды ограничений, которые могут вам понадобиться. Вам придется использовать более высокие типы, чтобы это работало должным образом.
person
Eric Lippert
schedule
10.12.2010
Functor
случае базовый класс абсолютно бессмысленен (потому чтоfmap
не может быть объявленvirtual
). - person lijie   schedule 10.12.2010