Подтвердить что ты не робот

Классные классы Haskell и классы шаблонов С++

Можно ли эмулировать функциональность класса классов Haskell с шаблонами С++ (или С#)?

Имеет ли смысл или есть ли какой-либо выигрыш в этом?

Я пытался создать класс Functor на С++, и я не смог. Я пробовал что-то вроде этого:

#include <iostream>
using namespace std;

//A function class to make types more readable
template <class input, class output> class Function {
private:
  output (*ptrfunc )(input);
public:
  Function(output (* ptr)(input)) {
    ptrfunc = ptr;
  }
  output call(input x) {return  (*ptrfunc)(x);}
  output operator() (input x) { return call(x);}
};


//the functor "typeclass"
template <class a> class Functor{
public:
  template <class b> Functor<b> fmap(Function<a,b> func);
};

// an container type to be declared "instance" of functor:
template <class a> class List : public Functor<a> { 
private:
  a * ptrList;
  int size;
public:
  List(int n) {  //constructor;
    ptrList = new a[n]; 
    size = n;
  }
  List(List<a> const& other) { //copy constructor
    size = other.size;
    ptrList = new a[size];
    for(int i = 0; i<size; i++)
      (*this)[i] = other[i];
  }
  ~List() { delete ptrList;} //destructor
  a& operator[](int i) { return ptrList[i];} // subscript operator just for easy notation
  const a& operator[](int i) const { return ptrList[i];}// subscript operator just for easy notation

  template <class b> List<b> fmap(Function<a,b> func) { //"instance" version of fmap
    List<b> temp(size);
    for(int i = 0; i < size; i++)
      temp[i] = func((*this)[i]);
    return temp;
  }
};


int test(int k) { return 2 * k;}

int main(void) {
  Function<int, int> func(&test);
  List<int> lista(10);
  for(int i = 0; i < 10; i++)
    lista[i] = i;
  List<int> lista2(lista.fmap(func));
  for(int i = 0; i < 10; i++)
    cout << lista2[i] << " ";
  cout << endl;
  return 0;
}

Он делает то, что он должен делать, но имеет ли смысл использовать этот шаблон в С++? Это действительно тот же шаблон, что и в haskell:

data List a = -- some stuff 

class  Functor f  where
fmap :: (a -> b) -> f a -> f b

instance (Functor List) where
-- some stuff    

Мне кажется, что это не то же самое, потому что в Functor f, f является конструктором типа * -> * типа, а в моем определении выше в Functor<a>, a не является template a<something>, но сам "содержащийся" тип данных.

Есть ли выход из этого? И что еще более важно: имеет смысл попытаться скопировать эти шаблоны на С++? Мне кажется, что С# больше похож на функциональный стиль программирования, чем на С++. Есть ли способ сделать это в С#?

4b9b3361

Ответ 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. Система типа С# просто недостаточно мощна, чтобы представлять все виды ограничений, которые могут вам понадобиться. Вы должны использовать более высокие типы, чтобы сделать эту работу должным образом.

Ответ 2

С++ 0x собирался ввести понятия, которые очень похожи на классы типа Haskell, но функция была отклонена. Будьте терпеливы еще десять лет, и они, наконец, могут проникнуть на язык.

Ответ 3

От автора этот пост в блоге на Haskell и С++:

Возможность выполнять вычисления времени компиляции в С++ была обнаружена, а не встроена в язык.

Хотя возможно эмулировать многие функции Haskell в шаблонах С++, переведенный синтаксис действительно уродлив. Это в основном потому, что метапрограммирование на С++ было скорее аварией, чем первоначально задуманной функцией.

Ответ 4

Хотя С# и С++ были выделены в сообщении, может быть интересно сравнить с Scala (2.8, цель - JVM). Scala очень похож на С#, поскольку он является сильным/статическим однонаправленным языком OO, но имеет более мощную систему типов, чем С# 3/4 (функции не являются полным перекрытием, однако).

Я не знаком с Haskell, но я читал различные статьи и слышал ряд аргументов на протяжении многих лет, и я считаю, что функциональность функциональности, которую вы ищете, может быть получена с помощью implicits (пример), даже если это не так уж идиоматично, как в Haskell.

С другой стороны, F #, "функциональный язык", имеет бедный (или несуществующий) еще раз, а не мою область, но я считаю невозможным создание поддержки типа Monad в F #) для конструкций типов. Это определенно платит, чтобы отбить сильные стороны языка.

Счастливое кодирование.

Ответ 5

Возможно, мне не хватает мета-уровня Haskell здесь, но ваш фактический пример без деклараций будет выглядеть так, используя STL:

struct Test : public std::unary_function<Foo,SomeResult> {
    SomeResult operator()( const Foo& foo ) const;
};

std::vector<Foo> foos;
...

std::vector<SomeResult> results;
std::transform( foos.begin(), foos.end(), results.begin(), Test() );
....