Классы типов
От: 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 видна
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.