Объясните, а?
Вот есть хаскельный код (пример упрощён до предела):
data P a = P a (forall b. b -> P (a, b))Нормально компилится, если указать прагму
LANGUAGE RankNTypes в GHC или ключик -98 в Hugs-е.Можно написать несколько "генераторов" для такого
P:sameValue :: a -> P aКак сделать это на C++???
sameValue x = P x (\y -> sameValue (x, y))
firstRest :: a -> a -> P a
firstRest x y = P x (\z -> sameValue (y, z))
switching :: a -> a -> P a
switching x y = P x (\z -> switching (y, z) (x, z))
Я попробовал, моего плюс-фу не хватило. Светилы, если вы есть, можете подсказать?
Хочется что-то вроде этого:
#include <map>
template <class A> class P {
A car;
template <class B> virtual P<std::pair<A,B> > cdr (B) = 0;
};
Не заработает, ибо
template и virtual вместе не живут. Убрать virtual нельзя - класс определяется как абстрактный, реализация функции cdr будет разной (см. выше), но эту разницу надо скрыть "под капотом", указывая везде базовый класс.Как?
Comments
Как с этой штукой работать-то?
f (P x _) (P _ g) = g x
или что-то в этом роде. Вложенность произвольная.
В общем, я почти сделал, но мне стало лень доводить до конца.
Суть в том, что в Хаскеле тип написан b, но его нельзя перенести как шаблонный параметр в Си++. Отличие в том, что в Си++ если есть шаблонный параметр типа, он точно известен. Тут же b как бы любой. Т.е. в Си++ в качестве аналога надо брать void*. Тогда cdr будет иметь вид void * -> P<std::pair<A, void*> >. + пишем шаблонную (уже невиртуальную) обёртку, которая эти void* преобразует в переданный B. Ошибки нет, так как мы в функцию передаём B, откастованный в void*, а полученный обратно void* - это тот самый B и есть (так же и в Хаскеле, с параметром неизвестного типа особо не наиграешься, только вернуть обратно в какой-либо комбинации) :) И мы кастуем его обратно.
Проблема в том, что мы назад ещё и функцию получаем (у меня был boost::function) и кастовать её и думать дальше мне стало лень, да и компилятор я этим кодом в итоге вроде подвесил.
Начиная с этой фразы, я перестал понимать.
Этот код неэквивалентен Хаскельному:
template <class B> virtual P<std::pair<A,B> > cdr (B) = 0;
Потому что вы знаете конкретный тип B и можете с разными типами делать разное. В Хаскеле - нет.
Эквивалентная замена:
P<std::pair<A, void*> > cdrImpl(void *)
И обёртка над ним:
template <class B> P<std::pair<A, B> > cdr(B b)
{
P<std::pair<A, void *> > retImpl = cdrImpl(static_cast<void *>(&b));
P<std::pair<A, B> > ret;
ret.car.first = retImpl.car.first;
ret.car.second = *static_cast<B *>(retImpl.car.second);
ret.cdrImpl = // как-нибудь кастануть и функцию
return ret;
}
И далее развитие этой мысли.
Чего?
тип функции a -> a в Хаскеле не эквивалентен типу
template <class A> A f(A a) в Си++
Он эквивалентен типу
void * f(void *)
поэтому и переписывать надо соответствующе
> template <class A> A f(A a) в Си++
Вот это я опять не понимаю.
То есть, нет, частично понимаю. Например, мы можем использовать что-нибудь типа a.doSomething() внутри тела f. То есть, реально это будет что-то типа (C a) => a -> a, где C - специальным образом подобранный класс. Эта разница имеется в виду?
> void * f(void *)
Нет, ну, поиметь проверку типов и я могу, это неинтересно.
А тип a -> b -> a чему эквивалентен, по-вашему?
По типу a -> a я вам даже реализацию напишу. И по типу a -> b -> a тоже. А по Си++сным шаблонам сказать нельзя решительно ничего, так как для каждого типа реализация может быть вообще своя.
Поэтому a -> b -> a вообще в Си++ не выражается. Наиболее близко:
void * f(void *, void *). Формально, даже void * f(void *) это по сути a -> b, так как указать, что на самом деле внутри void * лежит нечто того же типа, что и передали - никак нельзя.
Т.е. void* более слабо, так как теряется информация о типах вообще, а template более сильно, так как вся информация есть. Хаскельное a нечто среднее, так как с одной стороны тип-то мы не знаем, но мы можем по крайней мере указать, что принимаются два одинаковых и что возвращается такой же.
Видимо, полная эквивалетность вот такая:
a -> b -> a
void * fooImpl(void *, void *); // имплементация, внутри которой как раз типов и нет уже
template <typename A, typename B>
A foo(A, B) { fooImpl(...); } // обёртка для типизации
>Нет, ну, поиметь проверку типов и я могу, это неинтересно.
Т.е. речь по сути о том, что типизации разные в принципе. Поэтому иметь типизацию придётся, но частично.
Напрямую не переносится код ни туда, ни обратно.
Как например перенести с Си++ на Хаскель:
template <class A> A foo(A)
?
Одну?
> template <class A> A foo(A)
Ну дык foo :: a -> a. Оно понятно, что C++-ный вариант позволяет до жопы вариантов - см. выше про doSomething. Ну тогда можно, опять-таки, foo :: C a => a -> a.
Вопрос-то не в том, как типы переносить. Вопрос в том, как переносить конкретный КОД. Вот если вы мне напишете функцию foo, то я её запишу на хаскеле, задав тип как в предыдущем абзаце. А вот функцию f из моего первого коммента в этой дискуссии можно (по вашему) перенести в C++ только с грязными хаками. Вот я и хочу без них обойтись.
>Одну?
Рабочую одну. Ну там ещё error, undefined, throw и прочее из этой оперы. Или я чего-то не знаю? (я именно про a -> a, а не C a => a -> a)
>Вот если вы мне напишете функцию foo, то я её запишу на хаскеле, задав тип как в предыдущем абзаце
Да разве
template <class A> foo (A)
{ millionsOfSpecializations<A>::foo(); }
И как же?
>А вот функцию f из моего первого коммента в этой дискуссии можно (по вашему) перенести в C++ только с грязными хаками. Вот я и хочу без них обойтись.
Смотря что считать грязным хаком. B -> void * -> B - well-defined.
Если же сделать P<A, F>, где F - template <typename B> class, то будет и без хаков, но шаблон P станет зависеть так же и от функции. Это продиктовано именно тем, что в Си++ шаблон позволяет больше, чем a -> P (a, b) в Хаскеле.
Т.е. a -> b -> a по сути ничего не знает о типах в реализации. Но такая запись позволяет наложить некоторые ограничения (тип результата тот же, что и у первого аргумента). В Си++ это не выражается в принципе, а шаблон мощнее, так как в реализации шаблона мы знаем типы, и можем даже на этом знании основывать реализацию. Так что выбор - или делать мощнее (и пропихивать лишний шаблонный параметр), или нарушить ограничение в потрохах, а пропихнуть только наружу обёрткой, так как выразить его (ограничение) невозможно.
>А вот функцию f из моего первого коммента в этой дискуссии можно (по вашему) перенести в C++ только с грязными хаками
Не обратил внимания, кстати. Думал, Вы про функции из поста. Эту - можно:
template <class A, class B>
P<std::pair<B, A> > f(P<A> px, P<B> pg) { return pg.cdr(px.car); }