Классы типов
От: x-code  
Дата: 25.10.14 22:12
Оценка:
Читаю статью
http://habrahabr.ru/post/239151/
так и не могу в точности понять, что же такое "класс типов". Можете объяснить что это за концепция?
Re: Классы типов
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 25.10.14 22:56
Оценка: +1
Здравствуйте, x-code, Вы писали:

XC>Читаю статью

XC>http://habrahabr.ru/post/239151/
XC>так и не могу в точности понять, что же такое "класс типов". Можете объяснить что это за концепция?

Если грубо, то это как generic-интерфейсы, только конкретная реализация задаётся не в определении типа, а вне его.
Re: Классы типов
От: Evgeny.Panasyuk Россия  
Дата: 26.10.14 00:03
Оценка: 4 (1) +1
Здравствуйте, x-code, Вы писали:

XC>Читаю статью

XC>http://habrahabr.ru/post/239151/
XC>так и не могу в точности понять, что же такое "класс типов". Можете объяснить что это за концепция?

Если брать C++ STL — там есть такие концепции как Forward iterator, Random access iterator, Container, Associative container и т.п.
Для этих концепций есть определённые синтаксические и семантические требования. Например для Container, одно из требований это метод .size(), который должен возвращать значение типа size_type. Концепция это аналог "класса типов".

Типы которые удовлетворяют той или иной концепции, называются моделями соответствующей концепции. Например vector<double> это модель концепции Container, std::set<int> — это тоже модель концепции Container, при этом у них нет общих предков в ООП смысле.

Концепция — это своего рода аналог интерфейсов из ООП, только более обобщённый. Концепции могут задавать соотношения между типами в сигнатурах (например T operator+(T, T), то есть типы параметров и результата должны быть одинаковы), а не только жёстко прибивать конкретные типы (как это делают абстрактные классы из ООП — virtual int operator+(int, int)). Концепции не ограниченны описанием требований к членам моделей — например, они могут определять требования и к non-member functions.

Аналогично тому как в ООП один интерфейс может наследовать несколько других — одна концепция может являться уточнением более общих концепций. Например концепции Associative container и Sequence container — это уточнения концепции Container, они добавляют дополнительные требования к моделям.
Re: Классы типов
От: AlexRK  
Дата: 26.10.14 08:48
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Читаю статью

XC>http://habrahabr.ru/post/239151/
XC>так и не могу в точности понять, что же такое "класс типов". Можете объяснить что это за концепция?

Интерфейсы, реализация которых задается вне класса.
Таким образом, класс C можно передать в метод, требующий интерфейса I, если мы напишем в текущей области видимости реализацию I для C.
Если в текущей области видимости больше чем одна реализация I для C, то возникает конфликт, который надо разруливать.
Re[2]: Классы типов
От: x-code  
Дата: 26.10.14 17:58
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

Спасибо, стало значительно понятнее! Кстати, это как-то пересекается с понятием структурной типизации?
Re[3]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 26.10.14 18:26
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Здравствуйте, Evgeny.Panasyuk, Вы писали:


XC>Спасибо, стало значительно понятнее! Кстати, это как-то пересекается с понятием структурной типизации?


структурная типизация — частный случай сабжа
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 26.10.14 18:27
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Читаю статью

XC>http://habrahabr.ru/post/239151/
XC>так и не могу в точности понять, что же такое "класс типов". Можете объяснить что это за концепция?

Это контракт типа. Или интерфейс типа, в широком смысле.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[4]: Классы типов
От: x-code  
Дата: 26.10.14 18:52
Оценка:
Здравствуйте, jazzer, Вы писали:

J>структурная типизация — частный случай сабжа


Кстати, интересно, какие есть еще частные случаи сабжа. Для рассмотрения сабжа с максимального числа сторон...
Re[2]: Классы типов
От: Klapaucius  
Дата: 31.10.14 09:41
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Интерфейсы, реализация которых задается вне класса.

ARK>Таким образом, класс C можно передать в метод, требующий интерфейса I, если мы напишем в текущей области видимости реализацию I для C.
ARK>Если в текущей области видимости больше чем одна реализация I для C, то возникает конфликт, который надо разруливать.

Нет, классы типов — это не интерфейсы. Между интерфейсами и имплементирующими их классами существует отношение подтипирования. Между тайпклассом и типом, для которого определен его инстанс такого отношения нет. Т.е. скастить к тип к тайпклассу нельзя.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[3]: Классы типов
От: AlexRK  
Дата: 31.10.14 11:13
Оценка:
Здравствуйте, Klapaucius, Вы писали:

K>Нет, классы типов — это не интерфейсы. Между интерфейсами и имплементирующими их классами существует отношение подтипирования.


С чего вы взяли? Это особенность реализации.
В Java так и пишут — "implements", а наследование делается через другой синтаксис.

K>Между тайпклассом и типом, для которого определен его инстанс такого отношения нет. Т.е. скастить к тип к тайпклассу нельзя.


Ну так есть экзистенциальные типы, которые по сути есть то же самое, вид сбоку.
Re: Классы типов
От: Klapaucius  
Дата: 31.10.14 13:20
Оценка: 21 (2) +1
Для начала нужно сказать, что тайпклассами сейчас называют все что угодно, что может запутать кого угодно. В статье, на которую вы ссылаетесь упоминается Go, но говорить о классах типов в языках без параметрического полиморфизма просто бессмысленно.
В языках с параметрическим полиморфизмом значение параметризованного типа огорожено непробиваемым барьером абстракции. Параметр a означает "для любого типа", а над любым типом могут быть заданы или не заданы разные операции, могут быть не определены вообще никакие, минимального обязательного набора нет. Да, в некоторых языках, вроде C# квантификация всегда ограничена по крайней мере интерфейсом класса object, т.е. сделать можно много чего, но в нормальном параметрическом полиморфизме мы можем только куда-то помещать, передавать, или вовсе игнорировать значение, поэтому число осмысленных реализаций полиморфных функций может быть сильно ограничено.
На этом этапе думаю уже понятно, что рассуждающие про сиплюсплюсные темплейты и концепты по соседству не понимают о чем говорят, ничего общего все это с параметрическим полиморфизмом и классами типов не имеет.
Развеяв некоторые недоразумения, переходим к примерам.

Попробуем определить функцию, суммирующую элементы некоего контейнера, для которого свертка определены.
sum :: [a] -> a
sum = fold (+) 0

стоп, так не получится. Ни плюс, ни 0 использовать нельзя, это не C++, где операции могут быть задокументированы в виде концепта, но вообще это не требуется — тут параметрический полиморфизм. Чтоб что-то там суммировать, придется передать в функцию подходящие другие функции:
sum :: (a -> a -> a) -> a -> [a] -> a
sum (+) 0 = fold (+) 0

Протягивать все эти функции через параметры по одной — удовольствие сомнительное. К тому же, операции — в данном случае (+) и ноль — обычно сгруппированы не просто так, у этой группы операций есть свойства.
Для плюса и ноля это
0 + x = x
x + 0 = x

т.е. 0 — нейтральный элемент
(это рекомендуемое правило для разбиения операций на такие группы — у группы операций должны быть какие-то нетривиальные свойства)
Будем хранить и передавать такие операции вместе.
Определим для этой пары тип
data Add a = Add { plus :: a -> a -> a, zero :: a }

и перепишем функцию соотв. образом:
sum :: Add a -> [a] -> a
sum (Add (+) 0) = fold (+) 0

Определим готовые группы операций (словари) для типов и положим в библиотеку.
addInt :: Add Int
addInt = Add (+#) 0#

addDouble :: Add Double
addDouble = Add (+##) 0.0##

теперь
sumInt = [Int] -> Int
sumInt = sum addInt


На данном этапе у нас две проблемы:
1) Мы должны расставлять словари руками.
2) Мы можем ошибиться и разные части вычисления (сортировка, например) будут сделаны с разными словарями и ответ получится неверным.
Эти проблемы могут показаться после такого примера надуманными, но это вовсе не так: в реальности словари могут образовывать причудливые и развесистые структуры, собирать их руками без ошибок — это точно не то, чем мечтает заняться программист.

Тайплклассы задуманы так, чтоб решить обе эти проблемы.

class Add a where
    plus :: a -> a -> a
    zero :: a

instance Add Int where
    plus = (+#)
    zero = 0#

instance Add Double where
    plus = (+##)
    zero = 0.0#


Рассахаривается именно в описанные выше значения-словари.
Сигнатура функции sum меняется таким образом:
sum :: Add a => [a] -> a

Для классов типов (да — это уже они) перечисленных слева от двойной стрелки => компилятор найдет словари и передаст их в функцию через неявный параметр.
В данном случае, как мне кажется, узнавание деталей реализации способствует формированию интуитивного представления о том, что такое классы типов и как они работают.
Можно сказать, что многое из того, что вы ожидаете от такой модели, вроде:

и т.д. можно ожидать и от классов. Чего нельзя ожидать — так это свободы в выборе передаваемого словаря, потому что это несовместимо с решением проблемы 2. Особо нужно отметить, что бывают автоматически генерируемые словари-инстансы, которые никто нигде в коде вручную не описывает.

Помимо этого способа реализации, называемого "передача словаря", существуют и другие, например с помощью GADT, как в JHC http://repetae.net/computer/jhc/jhc-reify-typeclass.html , однако это довольно таки обходной путь объяснения классов типов. Кроме того, классы типов могут быть и вовсе фиктивными, не имеющими соответствующих передаваемых куда-то словарей функций.

Никакой структурной типизации, про которую тут по соседству говорили нет: тип, структурно эквивалентный типу с другим именем, для которого инстанс класса есть — не имеет инстанса этого класса, пока тот не будет определен явно, типизация тут номинативная. Правда, в случае "оберток", например, инстанс может быть выведен, но только после явного указания. Т.е. если без имплементации инстанса можно обойтись, то без декларации о том, что существует инстанс класса С для типа T — нет.

Обратите внимание на то, что имплементации существуют отдельно от значений типов. Они могут быть определены отдельно, они передаются в функции отдельно. Нет необходимости, например, хранить в каждом значении типа Int ссылку на словарь addInt. (хотя возможность такая есть, если нужна диспетчеризация в рантайме). Каждому типу соотвествует один инстанс класса, поэтому специального связывания данных с функцими не требуется.
На практике можно считать, что GHC "гарантирует" глобальную уникальность инстансов (проверки можно обойти несколькими способами, но они в достаточной степени хороши для того, чтоб на них можно было рассчитывать).

Первая проблема также решается в языках c неявными параметрами:
sum :: {Add a} -> [a] -> a

Параметр в фигурных скобках — неявный, компилятор попытается найти в области видимости подходящий словарь и подставит его. Никаких попыток поддержать глобальную уникальность инстансов тут не делается.
Проблема 2 в таких языках не решается даже на уровне, на котором она решается в GHC, т.е. рассчитывать на уникальность нельзя и простой перенос хаскельных практик работы с тайпклассами в эти языки из хаскеля невозможен. Хотя про такие языки обычно говорят, что тайпклассы в них есть, следует учитывать, что это наличие "с оговорками".
В таких языках модель передачи словаря можно воспринимать без всяких оговорок, так как никаких ограничений на ручной выбор передаваемого словаря они не накладывают.

Каноническое описание классов типов от их изобретателя — это статья "How to make ad-hoc polymorphism less ad hoc": http://www.cse.iitk.ac.in/users/karkare/courses/2010/cs653/Papers/ad-hoc-polymorphism.pdf
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[4]: Классы типов
От: Klapaucius  
Дата: 31.10.14 13:26
Оценка: +1
Здравствуйте, AlexRK, Вы писали:

ARK>С чего вы взяли? Это особенность реализации.

ARK>В Java так и пишут — "implements", а наследование делается через другой синтаксис.

И что? Причем тут наследование? Я про сабтайпинг говорю.

K>>Между тайпклассом и типом, для которого определен его инстанс такого отношения нет. Т.е. скастить к тип к тайпклассу нельзя.


ARK>Ну так есть экзистенциальные типы, которые по сути есть то же самое, вид сбоку.


Нет, не то же самое. По области применения некоторое пересечение есть.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[2]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 31.10.14 13:51
Оценка: 10 (1)
Здравствуйте, Klapaucius, Вы писали:

K>На этом этапе думаю уже понятно, что рассуждающие про сиплюсплюсные темплейты и концепты по соседству не понимают о чем говорят, ничего общего все это с параметрическим полиморфизмом и классами типов не имеет.


В том виде, в котором ты все сформулировал, это просто древняя сиплюсовая техника traits:
K>
K>class Add a where
K>    plus :: a -> a -> a
K>    zero :: a

K>instance Add Int where
K>    plus = (+#)
K>    zero = 0#

K>instance Add Double where
K>    plus = (+##)
K>    zero = 0.0#

K>

template<class a> struct Add; // без реализации, чтоб была ошибка компиляции для неизвестного типа

template<> struct Add<int> {
  static int plus(int x, int y) { return x+y; }
  static int zero() { return 0; }
};

template<> struct Add<double> {
  static double plus(double x, double y) { return x+y; }
  static double zero() { return 0.0; }
};

K>Рассахаривается именно в описанные выше значения-словари.
K>Сигнатура функции sum меняется таким образом:
K>
K>sum :: Add a => [a] -> a
K>

template<class a>
int sum(vector<a> v) {
  return std::accumulate( std::begin(v), std::end(v), Add<a>::plus, Add<a>::zero() );
}


причем std::begin и std::end — это тоже они (синтаксис несущественно упрощен):
// для вектора - честный итератор:
std::vector<a>::iterator std::begin(std::vector<a> v) { return v.begin(); }
std::vector<a>::iterator std::end(std::vector<a> v) { return v.end(); }
// для встроенного массива - указатель на первый/запоследний элемент:
a* std::begin(a[N] v) { return &v[0]; }
a* std::end(a[N] v) { return std::begin(v)+N; }


А тогда sum переформулируется для любых контейнеров, для которых определен класс begin/end:
template<class V>
auto sum(V v) {
  typedef decltype(*std::begin(v)) a; // тип элемента контейнера
  return std::accumulate( std::begin(v), std::end(v), Add<a>::plus, Add<a>::zero() );
}
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Классы типов
От: WolfHound  
Дата: 31.10.14 15:01
Оценка:
Здравствуйте, jazzer, Вы писали:

J>В том виде, в котором ты все сформулировал, это просто древняя сиплюсовая техника traits:

Проблема в том, что если написать так:
template<> struct Add<float> {
  static float zero() { return 0.0; }
};

Ошибка будет не при компиляции Add<float>, а при компиляции sum.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[4]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 31.10.14 16:00
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Здравствуйте, jazzer, Вы писали:


J>>В том виде, в котором ты все сформулировал, это просто древняя сиплюсовая техника traits:

WH>Проблема в том, что если написать так:
WH>
WH>template<> struct Add<float> {
WH>  static float zero() { return 0.0; }
WH>};
WH>

WH>Ошибка будет не при компиляции Add<float>, а при компиляции sum.

Естественно, потому что компиляция Add<float> происходит при компиляции sum.
Но можно это тривиально запихнуть в сигнатуру sum и получать ошибку в месте вызова, до компиляции самой функции
Просто не хотел перегружать пост деталями.
В любом случае будет ошибка вида "Add<float>::plus не определен".

Или ты имел в виду, что не будет отловлена ошибка определения самого Add<float> (отсутствие plus), до всяких sum и прочих пользователей?
Если да, то тут тоже можно придумать как извернуться (static_assert), но в любом случае это второстепенная проблема.
Потому что главное — что будет сгенерирована ошибка во время компиляции. В какой конкретно момент компиляции — это уже детали.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[5]: Классы типов
От: WolfHound  
Дата: 31.10.14 16:21
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Потому что главное — что будет сгенерирована ошибка во время компиляции. В какой конкретно момент компиляции — это уже детали.

Из-за этой детали мы получаем:
1)Отсутствие модульности. Мы не можем отдельно скомпилировать Add<float> и sum. После чего подключить их к третьему модулю и использовать совместно.
2)Кошмарные сообщения об ошибках. Причем как правило далеко от того места где была реальная ошибка.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[6]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 03:51
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Здравствуйте, jazzer, Вы писали:


J>>Потому что главное — что будет сгенерирована ошибка во время компиляции. В какой конкретно момент компиляции — это уже детали.

WH>Из-за этой детали мы получаем:
WH>1)Отсутствие модульности. Мы не можем отдельно скомпилировать Add<float> и sum. После чего подключить их к третьему модулю и использовать совместно.
Во-первых, можем, если все написано правильно. А если неправильно — то всяко использовать не сможем — компиляция сломается, хоть в С++, хоть в Хаскелле, просто в разных местах.
Во-вторых, это мелочи. Достаточно static_assert вставить после объявления, чтоб все валилось сразу, раз уж это так для тебя принципиально. (А концепты, вроде как, сразу все проверяют автоматом)

WH>2)Кошмарные сообщения об ошибках. Причем как правило далеко от того места где была реальная ошибка.

Концепты именно это и исправляют (причем эмуляции концептов есть и сейчас, как и (очень простые) техники по уменьшению количества печатаемых ошибок, просто их писать неудобно).

Так что ты говоришь о какимх-то мелочах, которых разруливаются так или иначе, в то время как Klapaucius говорил о том, что плюсовые шаблоны/концепты — это в принципе не тайпклассы.
Я очень сомневаюсь, что раннее сообщение об ошибке — это принципиальная вещь для него. По крайней мере, он говорил совершенно о других вещах.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: Классы типов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 01.11.14 08:00
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Так что ты говоришь о какимх-то мелочах, которых разруливаются так или иначе, в то время как Klapaucius говорил о том, что плюсовые шаблоны/концепты — это в принципе не тайпклассы.

J>Я очень сомневаюсь, что раннее сообщение об ошибке — это принципиальная вещь для него. По крайней мере, он говорил совершенно о других вещах.

Если что, есть работы на эту тему.
C++ Templates/Traits versus Haskell Type Classes (2005)
Re[8]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 10:19
Оценка:
Здравствуйте, samius, Вы писали:

S>Здравствуйте, jazzer, Вы писали:


J>>Так что ты говоришь о какимх-то мелочах, которых разруливаются так или иначе, в то время как Klapaucius говорил о том, что плюсовые шаблоны/концепты — это в принципе не тайпклассы.

J>>Я очень сомневаюсь, что раннее сообщение об ошибке — это принципиальная вещь для него. По крайней мере, он говорил совершенно о других вещах.

S>Если что, есть работы на эту тему.

S>C++ Templates/Traits versus Haskell Type Classes (2005)

А сама статья есть где-нибудь? А то по ссылке только то, что она цитирует...
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[9]: Классы типов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 01.11.14 10:32
Оценка: 17 (1)
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, samius, Вы писали:


J>А сама статья есть где-нибудь? А то по ссылке только то, что она цитирует...

у меня там же ссылка на PDF видна
Re[10]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 11:03
Оценка:
Здравствуйте, samius, Вы писали:

S>Здравствуйте, jazzer, Вы писали:


J>>Здравствуйте, samius, Вы писали:


J>>А сама статья есть где-нибудь? А то по ссылке только то, что она цитирует...

S>у меня там же ссылка на PDF видна

Спасибо! (у тебя ссылка слегка битая, поправь).

Ну там как раз и говорится:

Haskell type classes and various extensions can be emulated in C++ by C++ templates/traits and some amount of compile-time metaprogramming. ...C++ can easily handle other possible extensions to Haskell type classes.

jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[11]: Классы типов
От: Klapaucius  
Дата: 01.11.14 13:25
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Ну там как раз и говорится:

J>

J>Haskell type classes and various extensions can be emulated in C++ by C++ templates/traits and some amount of compile-time metaprogramming. ...C++ can easily handle other possible extensions to Haskell type classes.


Что тут удивительного? C++ "эмулирует" средства, добавляемые в язык с параметрическим полиморфизмом для того, чтоб получить ad-hoc полиморфизм. Т.е. полиморфизм, который в С++ основной. Правильнее было бы, наверное, сказать, что это хаскель "эмулирует" C++ средства.
Удивительно было бы, если бы C++ не "эмулировал" то, что в нем и так есть, а — наоборот — то, чего в нем нет — т.е. параметрический полиморфизм.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[3]: Классы типов
От: Klapaucius  
Дата: 01.11.14 13:32
Оценка:
Здравствуйте, jazzer, Вы писали:

Ох, опять двадцать пять.

sum :: forall a. Add a => [a] -> a


А какая сигнатура у вашей sum?

J>template<class a>
J>int sum(vector<a> v) {
J>  return std::accumulate( std::begin(v), std::end(v), Add<a>::plus, Add<a>::zero() );
J>}


Вы ее, например, попробуйте на псевдохаскеле написать и сразу все ясно станет.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[11]: Классы типов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 01.11.14 13:48
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, samius, Вы писали:


S>>у меня там же ссылка на PDF видна


J>Спасибо! (у тебя ссылка слегка битая, поправь).

Не выходит. вставляю
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.78.2151&rep=rep1&type=pdf

получаю в превью
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.78.2151&amp;rep=rep1&amp;type=pdf


J>Ну там как раз и говорится:

угу
Re[4]: Классы типов
От: Klapaucius  
Дата: 01.11.14 13:53
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Ошибка будет не при компиляции Add<float>, а при компиляции sum.


Вы будете смеяться, но неполная имплементация инстанса в хаскеле — не ошибка, а ворнинг:

Prelude> data D = D
Prelude> class C a where c :: a

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 4, types: 7, coercions: 2}
-- тут сгенерировалась полиморфная функция, принимающая словарь.
c
c = \ @ a tpl -> tpl `cast` ...

-- пишем пустой инстанс:
Prelude> instance C D

-- компилятор выдает ворнинг
<interactive>:13:10: Warning:
    No explicit implementation for
      ‘c’
    In the instance declaration for ‘C D’

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 5, types: 4, coercions: 3}

-- После чего генерирует функцию-заглушку, кидающую эксепшн
$cc
$cc = noMethodBindingError "<interactive>:13:10-12|c"#

-- и делает словарь с ней (когда функция класса одна - словарь это просто обернутая в newtype функция)
$fCD
$fCD = $cc `cast` ...


В главном, впрочем, вы правы.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[12]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 15:04
Оценка:
Здравствуйте, Klapaucius, Вы писали:

K>Здравствуйте, jazzer, Вы писали:


J>>Ну там как раз и говорится:

J>>

J>>Haskell type classes and various extensions can be emulated in C++ by C++ templates/traits and some amount of compile-time metaprogramming. ...C++ can easily handle other possible extensions to Haskell type classes.


K>Что тут удивительного? C++ "эмулирует" средства, добавляемые в язык с параметрическим полиморфизмом для того, чтоб получить ad-hoc полиморфизм. Т.е. полиморфизм, который в С++ основной. Правильнее было бы, наверное, сказать, что это хаскель "эмулирует" C++ средства.

K>Удивительно было бы, если бы C++ не "эмулировал" то, что в нем и так есть, а — наоборот — то, чего в нем нет — т.е. параметрический полиморфизм.

А чем тебе traits не параметрический полиморфизм? Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой).
Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции.
Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[13]: Классы типов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 01.11.14 15:30
Оценка:
Здравствуйте, jazzer, Вы писали:

J>А чем тебе traits не параметрический полиморфизм? Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой).

J>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции.
J>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.
traits и type classes это как раз ad-hoc (https://www.haskell.org/haskellwiki/Polymorphism#Ad-hoc_polymorphism).
ПП работает для любого типа. Он unconstrained. Потому, любая функция, которой нужно ограничение (traits, type classes), не может быть ПП.
Re[4]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 15:57
Оценка:
Здравствуйте, Klapaucius, Вы писали:

K>Здравствуйте, jazzer, Вы писали:


K>Ох, опять двадцать пять.


K>
K>sum :: forall a. Add a => [a] -> a
K>


K>А какая сигнатура у вашей sum?


K>
J>>template<class a>
J>>a sum(vector<a> v) {
J>>  return std::accumulate( std::begin(v), std::end(v), Add<a>::plus, Add<a>::zero() );
J>>}
K>


K>Вы ее, например, попробуйте на псевдохаскеле написать и сразу все ясно станет.


Если ты намекаешь на то, что Add должна быть в сигнатуре, то, во-первых, я тебе ее и на С++ напишу:
template<class a>
typename enable_if< CheckTypeClass<Add, a>, a >::type
sum(vector<a> v);

где CheckTypeClass проверяет, что Add<a> определен и делает то, что надо.
А во-вторых, именно этим и занимаются концепты в явном виде:
template<Add a>
a sum(vector<a> v);

использование Add вместо обобщенного class как раз и означает проверку концепта (т.е. тайпкласса).

Вариант реализации CheckTypeClass (просто проверка сигнатур plus и zero):
template<class a>
struct CheckTypeClass<Add, a>
  : and_< is_same< decltype(Add<a>::plus), a(a, a) >
        , is_same< decltype(Add<a>::zero), a() >
        >
{};


Соответственно, возвращаясь к примеру Wolfhound-а, если воткнуть проверку
static_assert(CheckTypeClass<Add,float>::value,"Add<float> is not correct");

сразу после объявления неполного Add<float> без zero, то мы мгновенно получим сообщение об ошибке:
main.cpp: In instantiation of 'struct CheckTypeClass<Add, float>':
main.cpp:34:40:   required from here
main.cpp:27:136: error: 'zero' is not a member of 'Add<float>'

до объявления sum, всё, как хотел Wolfhound.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[13]: Классы типов
От: Klapaucius  
Дата: 01.11.14 16:09
Оценка:
Здравствуйте, jazzer, Вы писали:

K>>Что тут удивительного? C++ "эмулирует" средства, добавляемые в язык с параметрическим полиморфизмом для того, чтоб получить ad-hoc полиморфизм. Т.е. полиморфизм, который в С++ основной. Правильнее было бы, наверное, сказать, что это хаскель "эмулирует" C++ средства.

K>>Удивительно было бы, если бы C++ не "эмулировал" то, что в нем и так есть, а — наоборот — то, чего в нем нет — т.е. параметрический полиморфизм.

J>Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой).


Ну все правильно. Параметрический полиморфизм — это когда один код для любых типов. Соответственно полиморфный код, типизируется, компилируется раздельно, непробиваемая абстракция, есть полиморфная рекурсия и т.д. Но нам нужно и код для каждого типа отдельный писать.
Когда код отдельный для каждого типа — это ad-hoc полиморфизм.
Не понятно только, почему "лишь"? Куда уж больше отличая-то между полиморфизмами?

Prelude Data.Monoid> let msum xs = foldr (<>) mempty xs

-- получается один код для всех типов:
==================== Simplified expression ====================
returnIO
  (: ((\ @ b $dMonoid xs -> foldr (<> $dMonoid) (mempty $dMonoid) xs)
      `cast` ...)
     ([]))


msum :: Monoid b => [b] -> b
-- это была параметрически полиморфная часть.
Prelude Data.Monoid> data D = D

-- а теперь пишем код специфический для данного типа.
Prelude Data.Monoid> instance Monoid D where D `mappend` D = D; mempty = D

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 15, types: 13, coercions: 0}
-- ad-hoc для типа D
$cmappend
$cmappend = \ ds ds1 -> case ds of _ { D -> ds1 }

Rec {
-- собираем в словарь
$fMonoidD
$fMonoidD = D:Monoid D $cmappend $cmconcat
-- тут у нас дефолтная реализация
$cmconcat
$cmconcat = $dmmconcat $fMonoidD
end Rec }


J>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции.


Ну и где эта единственная реализация? Будет по реализации для каждого типа.
Можно конечно и параметрический полиморфизм в C++ эмулировать, боксируя все, и делая "параметрическую" функцию, работающую со ссылками.
Но смысл "эмулировать" ad-hoc полиморфизм в C++ — где он и так всегда работает — от меня все еще ускользает.

J>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.


Ну так в хаскеле-то она единственная без кавычек. Понятно, что — при наличии разверток — при оптимизации может появится несколько специализированных функций — но в хаскеле это деталь реализации, она семантически ненаблюдаема. В C++ весь темплейтный код на наблюдаемости такой "генеративной" реализации и основан.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[5]: Классы типов
От: Klapaucius  
Дата: 01.11.14 16:16
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Если ты намекаешь на то, что Add должна быть в сигнатуре, то, во-первых, я тебе ее и на С++ напишу:

J> <...>
J>сразу после объявления неполного Add<float> без zero, то мы мгновенно получим сообщение об ошибке:
J>
J>main.cpp: In instantiation of 'struct CheckTypeClass<Add, float>':
J>main.cpp:34:40:   required from here
J>main.cpp:27:136: error: 'zero' is not a member of 'Add<float>'
J>

J>до объявления sum, всё, как хотел Wolfhound.

Ну, концепты — это опциональная "проверяемая компилятором документация", без которой и так все работает. А тайпкласс — именно то, что и работает.
у вашей функции sum один параметр vector<a> v. У моей хаскельной версии на самом деле три параметра.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[14]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 16:17
Оценка:
Здравствуйте, samius, Вы писали:

S>Здравствуйте, jazzer, Вы писали:


J>>А чем тебе traits не параметрический полиморфизм? Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой).

J>>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции.
J>>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.
S>traits и type classes это как раз ad-hoc (https://www.haskell.org/haskellwiki/Polymorphism#Ad-hoc_polymorphism).
S>ПП работает для любого типа. Он unconstrained. Потому, любая функция, которой нужно ограничение (traits, type classes), не может быть ПП.

Во-первых, Что значит — работает? Типы ведь очень разные бывают. Мне как-то сложно представить функцию, которая будет работать абсолютно для всех типов, кроме identity (и то только для копируемых/перемещаемых типов) и констант типа примера ниже.

Во-вторых, обычные сиплюсовые шаблоны в каком-то смысле "работают" для всех типов изначально, до того, как были изобретены traits:
template<class a> byte bestArchiverEver(a) { return byte(1); }

для любого типа. unconstrained.
Или что там в статье приводят в качестве пример, map?
map :: (a -> b) -> [a] -> [b]

Ну так и в С++ он будет такой же:
template<class a, class b> list<b> map( b(a), list<a> );

Так что мне не очень понятно, когда Klapaucius говорит, что в С++ нет ПП.

Ну и в-третьих, traits, type classes — это не (только) ограничение, а, если можно так выразиться, средство натянуть сову на глобус.
Т.е. рассказать, как функции надо работать с типом, который без этого этой функции не подходил бы.
Как в примере со встроенным массивом, у которого нет методов begin/end — но их можно предоставить извне, и тогда функция std::accumulate будет работать со всем подряд. Но (судя по статье, которую ты привел) это Ad-hoc_polymorphism и есть.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[14]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 16:31
Оценка:
Здравствуйте, Klapaucius, Вы писали:

K>Не понятно только, почему "лишь"? Куда уж больше отличая-то между полиморфизмами?

Потому что пока у нас одна функция — это параметрический полиморфизм.
Как только добавили к ней перегрузку/специализацию для какого-нть типа — все, ad hoc.
По мне так деталь абсолютно несущественная, чтоб еще и специальным названием заморачиваться и следить за правильной терминологией

J>>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции.

K>Ну и где эта единственная реализация? Будет по реализации для каждого типа.

В коде, естественно. Или ты про бинарник говришь? Имхо, это вообще никакого отношения к делу не имеет — как там что на бинарном уровне генерится.

K>Можно конечно и параметрический полиморфизм в C++ эмулировать, боксируя все, и делая "параметрическую" функцию, работающую со ссылками.

K>Но смысл "эмулировать" ad-hoc полиморфизм в C++ — где он и так всегда работает — от меня все еще ускользает.
Ну так и параметрический работает "из коробки", не?

J>>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.


K>Ну так в хаскеле-то она единственная без кавычек.

Где ж без кавычек? Вон у sum единственная реализация, только вот вся реальная работа вынесена в Add.
Так что если рассматривать sum как исключительно ее код — все хорошо.
А если принять во внимание Add и различия в его реализации — то сразу получится много всего.
Так что это вопрос чисто терминологический — насколько глубоко спускаться, определяя единственность реализации.
Мне вот лично кода вполне достаточно. Если тело функции определено (в тексте программы) один раз — значит, у нее единственная реализация. Что она там зовет из себя — ее личное, десятое, дело.

K>Понятно, что — при наличии разверток — при оптимизации может появится несколько специализированных функций — но в хаскеле это деталь реализации, она семантически ненаблюдаема. В C++ весь темплейтный код на наблюдаемости такой "генеративной" реализации и основан.


где? Вроде только сигнатуры проверяются же, кого колышет бинарный код?
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[6]: Классы типов
От: jazzer Россия Skype: enerjazzer
Дата: 01.11.14 16:36
Оценка:
Здравствуйте, Klapaucius, Вы писали:

K>Ну, концепты — это опциональная "проверяемая компилятором документация", без которой и так все работает. А тайпкласс — именно то, что и работает.


traits в С++ — это тоже "именно то, что и работает".
И моя Add в С++ — тоже "именно то, что и работает".
Плюс она таки в сигнатуре, как и у тебя Add в Haskell.

K>у вашей функции sum один параметр vector<a> v. У моей хаскельной версии на самом деле три параметра.


Можно раскрыть мысль про три параметра? Я вот смотрю глазами и вижу только один — [a].
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[15]: Классы типов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 01.11.14 17:26
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, samius, Вы писали:


J>>>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.

S>>traits и type classes это как раз ad-hoc (https://www.haskell.org/haskellwiki/Polymorphism#Ad-hoc_polymorphism).
S>>ПП работает для любого типа. Он unconstrained. Потому, любая функция, которой нужно ограничение (traits, type classes), не может быть ПП.

J>Во-первых, Что значит — работает? Типы ведь очень разные бывают. Мне как-то сложно представить функцию, которая будет работать абсолютно для всех типов, кроме identity (и то только для копируемых/перемещаемых типов) и констант типа примера ниже.

работает — значит handle values. Может и не значит, но я употребил "работает" примерно в этом контексте.
можно еще представить функцию length, которая возвращает длину списка для списка с любым типом элемента списка. А если напрячься, то еще и : (cons)...

J>Во-вторых, обычные сиплюсовые шаблоны в каком-то смысле "работают" для всех типов изначально, до того, как были изобретены traits:

Некоторые шаблоны работали для всех, а какие-то за счет перегрузок. Т.е. еще не traits, но уже ad-hoc
J>
J>template<class a> byte bestArchiverEver(a) { return byte(1); }
J>

J>для любого типа. unconstrained.
а мне кажется что a должен уметь передаваться по значению.

J>Или что там в статье приводят в качестве пример, map?
map :: (a -> b) -> [a] -> [b]

J>Ну так и в С++ он будет такой же:
template<class a, class b> list<b> map( b(a), list<a> );

J>Так что мне не очень понятно, когда Klapaucius говорит, что в С++ нет ПП.
На самом деле у этого map есть неявный констрейнт на тип b, который накладывает list, в том числе при добавлении элемента.

J>Ну и в-третьих, traits, type classes — это не (только) ограничение, а, если можно так выразиться, средство натянуть сову на глобус.

J>Т.е. рассказать, как функции надо работать с типом, который без этого этой функции не подходил бы.
J>Как в примере со встроенным массивом, у которого нет методов begin/end — но их можно предоставить извне, и тогда функция std::accumulate будет работать со всем подряд. Но (судя по статье, которую ты привел) это Ad-hoc_polymorphism и есть.
Согласен. Ограничение (constraint) как средство. И даже как цель.
Re[15]: Классы типов
От: Klapaucius  
Дата: 01.11.14 20:13
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Потому что пока у нас одна функция — это параметрический полиморфизм.

J>Как только добавили к ней перегрузку/специализацию для какого-нть типа — все, ad hoc.

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

J>По мне так деталь абсолютно несущественная, чтоб еще и специальным названием заморачиваться и следить за правильной терминологией


Ну это типичный прием в таких разговорах. Регулярно на каком то этапе оказывается, что между чем угодно и чем угодно разницы нет, а значит если в C++ что-то есть, то есть и что угодно другое.
Если для вас нет разницы между параметрическим полиморфизмом и ad-hoc, то о чем вы вообще спорите? Какой-то полиморфизм в C++ есть — значит все в порядке. Какой — не важно.

J>В коде, естественно.


Который отдельно от другого кода не типизируется, а типизироваться будут так раз по функции на каждый тип, в отличие от хаскеля, где полиморфную функцию можно типизировать.

J>Или ты про бинарник говришь?


Раздельная компиляция — это только ожидаемое следствие раздельной типизации. Впрочем, у плюсовых шаблонов есть генеративные родственники, которые тоже отдельно не компилируются, зато типизируются: параметризованные модули в sml и inline-функции в F#.

J>Имхо, это вообще никакого отношения к делу не имеет — как там что на бинарном уровне генерится.


И типизируемость и раздельная компиляция как раз имеют отношение к делу. Нет никакой пользы от модели "параметрический полиморфизм", если мы к ней прибавляем: "но ничего из того, что вы ожидаете от параметрического полиморфизма не работает".

J>Ну так и параметрический работает "из коробки", не?


Нет, не работает. Его нет.

JK>>Ну так в хаскеле-то она единственная без кавычек.

J>Где ж без кавычек? Вон у sum единственная реализация, только вот вся реальная работа вынесена в Add.

Вовсе нет. Тот код, который обходит список, весь параметрически полиморфный. Чем это не "реальная работа", по сравнению со сложением?

J>Так что если рассматривать sum как исключительно ее код — все хорошо.

J>А если принять во внимание Add и различия в его реализации — то сразу получится много всего.

Ну так все хорошо, потому что в хаскеле мы можем рассматривать sum и Add раздельно. В C++ с другой стороны sum без Add — это нетипизированный и некомпилируемый код.

J>Так что это вопрос чисто терминологический — насколько глубоко спускаться, определяя единственность реализации.


Дело тут не в глубине спуска.

J>Мне вот лично кода вполне достаточно. Если тело функции определено (в тексте программы) один раз — значит, у нее единственная реализация. Что она там зовет из себя — ее личное, десятое, дело.


Ну а мне кода, который ни типизировать, ни скомпилировать, да и понять как следует нельзя — недостаточно.

J>где? Вроде только сигнатуры проверяются же, кого колышет бинарный код?


Сигнатуры не проверяются, пока вы собственноручно проверку сигнатур не напишете. Что характерно, в таких спорах я еще не разу не видел такого, чтоб проверку сразу написали, хотя все "упражнение" в общем то в ней и заключается. От опциональной типизации пользы примерно столько же, сколько от отсутствующей. Вся полезность от тайпчека как легковесной верификации — от обязательности.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[7]: Классы типов
От: Klapaucius  
Дата: 01.11.14 20:27
Оценка:
Здравствуйте, jazzer, Вы писали:

K>>Ну, концепты — это опциональная "проверяемая компилятором документация", без которой и так все работает. А тайпкласс — именно то, что и работает.


J>traits в С++ — это тоже "именно то, что и работает".

J>И моя Add в С++ — тоже "именно то, что и работает".
J>Плюс она таки в сигнатуре, как и у тебя Add в Haskell.

Нет, это не то, что работает. Это ненужные приседания. В темплейтной функции суммирования можно + писать без всяких передач словарей. Раздельной типизации и компиляции, которые мы и оплачиваем этими "приседаниями" в хаскеле — в плюсах все равно нет.

K>>у вашей функции sum один параметр vector<a> v. У моей хаскельной версии на самом деле три параметра.


J>Можно раскрыть мысль про три параметра? Я вот смотрю глазами и вижу только один — [a].


Не знаю, куда вы смотрите. Посмотрите в код в моих сообщениях, там все параметры отлично видны.
Могу повторить код с типами, которые я совершенно напрасно отфильтровал в прошлый раз:
Prelude Data.Monoid> let msum xs = foldr (<>) mempty xs

==================== Simplified expression ====================
returnIO  -- на эти навороты можно внимания не обращать
  @ [()]  -- это из-за объявления в интерпретаторе
  (: @ () -- рассахаренная функция - лямбда ниже
     ((\ (@ b) ($dMonoid :: Monoid b) (xs :: [b]) ->
         foldr @ b @ b (<> @ b $dMonoid) (mempty @ b $dMonoid) xs)
      `cast` ...)
     ([] @ ())) 

-- сюда я явно параметр дописал, сейчас - думаю - будет понятнее.
msum :: forall a. Monoid b => [b] -> b

Prelude Data.Monoid> instance Monoid D where D `mappend` D = D; mempty = D

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 15, types: 13, coercions: 0}

$cmappend :: D -> D -> D
$cmappend = \ (ds :: D) (ds1 :: D) -> case ds of _ { D -> ds1 }

Rec {
$fMonoidD :: Monoid D
$fMonoidD = D:Monoid @ D D $cmappend $cmconcat

$cmconcat :: [D] -> D
$cmconcat = $dmmconcat @ D $fMonoidD
end Rec }
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.