Ответ 1
Можно ли эмулировать функциональность класса классов Haskell с шаблонами С++ (или С#)?
Я недостаточно разбираюсь в шаблонах на С++, чтобы ответить на вопрос. Я могу немного рассказать о типах С#, хотя.
Короткий ответ: нет. Система Haskell классов "высшего" класса более мощна, чем система общего типа в С#.
Краткое обсуждение того, что мы подразумеваем под "более высокими типами", может быть полезно в этот момент для любых читателей, которые все еще читают это, которые не знакомы с Haskell. В С# вы можете сделать это:
interface IEnumerable<T> { ... }
"Общий тип" "IEnumerable одного параметра типа" на самом деле не является "типом" как таковой, это шаблон, из которого вы можете построить бесконечно много новых типов, заменив аргументы типа ( "int" ) на тип параметры ( "T" ). В этом смысле он "выше", чем обычный тип.
Вы можете установить ограничения на параметры типа общих типов:
class C<T> where T : IEnumerable<int>
Общий тип C может быть построен с любым аргументом типа, если аргумент типа является типом, который неявно конвертируется посредством ссылки или преобразования бокса в IEnumerable<int>
.
Но система типа Haskell идет на один шаг дальше этого. Он поддерживает "классы типов", где ограничения, которые вы можете наложить на T, такие вещи, как "T имеет на нем оператор равенства". В С# операторы определяются как статические методы, и нет аналога интерфейса для статических методов. В С# мы не можем обобщить многие типы на основе произвольных статических методов.
Примером обычно для Haskell является шаблон "монада". В обозначении С# предположим, что у нас есть тип:
class MyMonad<T>
{
public static MyMonad<TOut> Bind<TIn, TOut>(MyMonad<TIn>, Func<TIn, MyMonad<TOut>>) { ... }
public MyMonad(T t) { ... }
}
Монада - всего лишь образец; монадическим типом является любой общий тип, так что он имеет статический общий метод Bind и конструктор, который соответствует шаблону выше. В Haskell вы можете использовать более высокие типы для описания этого шаблона; в С# у нас нет возможности в системе типов для обобщения на такие вещи, как статические методы и конструкторы.
Или вы могли бы сказать, что было бы более идиоматично использовать привязку экземпляра:
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>>);
}
но мы не можем выразить это в С#.
Рассмотрим пример вашего Functor. В С# у нас может быть тип
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)
{ ... }
Или, опять же, более идиоматично, у нас может быть статический метод как метод расширения. (Фактически, этот метод существует в библиотеке операторов последовательности в С#, его можно построить, составив "Select" на IEnumerable<T>
с "ToList" ).
Безотносительно. Не имеет значения. Дело в том, что в вашем коде в Haskell:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Можно сказать, что "любой родовой тип, который предоставляет операцию сопоставления, которая соответствует шаблону выше, называется" Functor ", а затем вы можете создавать методы, которые принимают функции" Функторы ". У нас нет никакого способа обобщения" всех типов, которые предоставляют операции отображения" на уровне пользователя на С#.
Чтобы обойти это ограничение системы типов, мы сделали несколько наиболее мощных высших типов и построили их непосредственно на языке. Сам язык распознает более высокие типы, такие как шаблон последовательности (в обработке цикла foreach), обобщенный шаблон монады (при понимании запросов; "SelectMany" - "Bind" на произвольной монаде), шаблон продолжения (в "ожидании", входящем в С# 5), "Возможно" монада (в типах с нулевым значением) и т.д.
Итак, чтобы решить вашу конкретную проблему, да, без проблем, идея создания проецирования не фиксируется в системе типов, но она фиксируется на языке с помощью запросов LINQ. Если вы скажете
from x in y select z
тогда будет работать любое выражение y типа, которое имеет метод Select, который принимает y и отображение из x в z. Этот шаблон был встроен в язык С#. Но если вы хотите описать какой-то другой "более высокий" шаблон, вам не повезло.
Было бы неплохо иметь средство в системе типов для описания более высоких типов, но более вероятно, что мы будем поддерживать систему типа as-is и выпекать больше шаблонов на языке по мере необходимости.
В этом вопросе описывается место, где я обычно вижу попытку эмулировать типы более высокого порядка в С#:
Почему это обобщенное ограничение компилируется, когда оно похоже на круговую ссылку
Идея здесь заключается в том, что разработчик хочет разработать тип "Animal" таким образом, что:
abstract class Animal
{
public abstract void MakeFriends<T>(T newFriend)
where T : THISTYPE;
}
Вымышленный "где T: THISTYPE" пытается понять, что кошка может только подружиться с другим котом, Собака может только подружиться с другой Собакой и так далее. (Игнорируйте на данный момент тот факт, что такой шаблон, который подразумевает, что MakeFriends имеет виртуальную ковариацию на формальных типах параметров, не будет типичным и, вероятно, тем самым нарушит Принцип замещения Лискова.) Эта концепция выражается в более высоких типах, но не в система типа С#. Люди иногда используют шаблон, например:
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){ ... }
}
И теперь Собака может дружить с Кошкой, нарушая намерение автора Animal. Система типа С# просто недостаточно мощна, чтобы представлять все виды ограничений, которые могут вам понадобиться. Вы должны использовать более высокие типы, чтобы сделать эту работу должным образом.