Как стать автором
Обновить

Комментарии 14

Зачем выделять дополнительные условия 4 и 5 в определении полукольца? Они же следуют из свойств моноидов и дистрибутивности.

Вы правы, однако, думаю, автор оригинального поста рассматривал классическое определение, когда эти свойства вынесены в аксиомы полукольца. В разной литературе определяют по-разному, но, видимо, такое определение показалось ему более иллюстративным.

В eitherCardinality не забыли вычесть мощность пересечения множеств значений типов?

Нет, с `eitherCardinality` всё верно. В Scala `Either[A, B]` представлен двумя объектами: `Left[A, B]`, содержащим объекты типа `A`, и `Right[A, B]`, содержащим объекты типа `B`. `Left` с `Right` никак не пересекается, поэтому получаем строгую сумму.

Говоря о сумме типов вообще, можно провести аналогию, что сумма типов — это контейнер с ячейками, каждую из которых может занимать какой-либо тип, но всего может быть занята ровно одна ячейка. Соответственно, значения суммы типов — это «контейнер с заполненной ячейкой 1», «контейнер с заполненной ячейкой 2» и т.д. Таким образом, опять получаем строгую сумму, даже если типы в ячейках совпадают.

Т.е. нет "наследования" типов? (Integer)2 и (Number)2 обязательно разные вещи?

Не понял нотацию — поясните, что вы имеете в виду.

Какое-нибудь включение множеств значений типов друг в друга. Пусть E = Either[N, Z], где Either — сумма типов N и Z, как оно применяется в статье, N — тип, элементами которого являются только все натуральные числа меньше 3 (пусть с нулём), Z — тип, элементами которого являются только все целые числа меньше 3 по модулю. Я интуитивно ожидаю кардинальное число |E| = 5.

Я так понимаю, что вы пытаетесь рассматривать сумму типов как объединение множеств значений этих типов, но эти операции не совпадают.


В данном случае речь не идёт о каком-либо наследовании или включении одного типа в другой. Каждому из суммируемых типов можно сопоставить индекс, а значение суммы типов — это по сути "индекс" + "значение типа с соответствующим индексом в сумме". То есть для значения суммы типов важно, какой именно по порядку тип выбран.


Пример с Either[A, B] наиболее иллюстративен, так как Left[Int, Int](1) != Right[Int, Int](1), потому что различаются "обёртки"

Разумеется, пытался рассматривать как объединение. Ок, спасибо.
Как-то я не ожидал, что предполагается отказ от наследования

И в первом примере дополнения откуда степень? "A => B" — тип функции? Тогда достаточно перемножить мощности множеств значений A и B, нет?

Да, A => B — тип функции. Интуитивно понятно, что, если бы мы просто перемножили кардинальные числа, то получили бы результат, аналогичный произведению типов. Но количество функций из A в B в общем случае с количеством пар (A, B) не совпадает.


Откуда же берётся формула B^A? Рассмотрим произвольный элемент типа A. Функция A => B может переводить его в любой элемент типа B, значит, всего |B| вариантов. Рассмотрим следующий элемент типа A. Для него ситуация аналогичная — |B| вариантов. Это будет выполняться для любого представителя A. Соответственно, чтобы получить общее количество вариантов по превращению A в B, перемножим количество вариантов для отдельных представителей: |B| * |B| * |B| * |B| ... (всего |A| раз). Отсюда и следует |B => A| = |B|^|A|.


Аналогичным образом мы можем подумать о функции B => A как о представителе произведения типов (B, B, B, ..., B) (всего |A| раз). Элементами этого произведения можно однозначно задать любую функцию, а как вычислять кардинальное число такого типа уже известно.

А что насчет строк? String — формально неограниченный тип и теоретически обладает бесконечным числом значений (на практике, естественно, мы не обладаем бесконечной памятью, поэтому конкретное число будет зависеть от конфигурации компьютера).

String в текущих реалиях совсем не бесконечный. В JVM он точно не будет длиннее Integer.MAX_VALUE байт (так как содержит byte[] адресуемый целым числом). Причем не удивлюсь, если из-за деталей реализации окажется еще более ограниченным. Это не так много — 2 ГиБ памяти, а это уже не ограничение даже для современных телефонов. Да, конечно, кардинальное число exp(2,(8*(exp(2,31)-1)) изрядно большое (и даже в BigInteger в Java не влезет), но всё-таки эти пределы уже вполне можно "пощупать".

Еще мне из дополнения 2 момента непонятны
Первое:


Cardinality.of[Option[A]] === Cardinality.of[A] + 1

А что будет для Option[Option[Option[A]]]? Насколько я понимаю, новых значений там не появится — всё также будет Cardinality.of[A] + 1.


Второе — про Cardinality.of[A => B].
Я правильно понимаю, что это всё только про чистые детерминированные функции (иначе Unit=>Unit не счесть).

Для Option не происходит удаление вложенных None, поэтому формула всё-таки остаётся верной. Вот все значения, которые может принимать такой тип:
Option[Option[A]] = None, Some(None), Some(Some(A)). Кардинальное число: |A| + 1 + 1.


По поводу функций — дельное замечание, мне, наверное, стоило об этом упомянуть. Речь, естественно, идёт не о функциях Scala как таковых, а о морфизмах. Потому что даже чистых детерминированных функций Boolean => Boolean можно набрать бесконечное число, так как True можно представить как True && True, True && True && True и т.д. Cardinality.of[A => B] — это количество возможных вариантов перевести элементы A в элементы B простым сопоставлением/таблицей.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации