Код написан на Scala версии 3.3.1.

Что такое класс типов?

Давайте разберем, что такое класс типов. Обратимся к формальному определению:

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

Класс типов - это в первую очередь про "поведение". Когда мы определяем класс типов, то неявно заключаем "контракт", в котором описываем желаемое для определяемого класса типов поведение.

Является ли классом типов следующее определение?

trait Something[A, B]:
  def doSomething(x: A): B

Формально - да. Здесь есть какое-то определение какого-то поведения. Какое-то? Какого-то? Порой определения недостаточно, чтобы составить контракт. Необходимо уточнить определение класса типов. Например, возьмем полугруппу.

trait Semigroup[A]:
  def combine(x: A, y: A): A

Полугруппа должна следовать следующим законам:

  • Замыкание: для ∀ x, y ∈ S выполняется x + y ∈ S

  • Ассоциативность: для ∀ x, y, z ∈ S выполняется (x + y) + z = x + (y + z)

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

Второй закон также можно гарантировать, определив соответствующий метод проверки:

object Semigroup:
  def isSemigroup[A](x: A, y: A, z: A)(using sm: Semigroup[A]): Boolean =
    import sm.combine
    combine(x, combine(y, z)) == combine(combine(x, y), z)

Конечно, при условии, если бы мы могли запустить эту проверку на всех возможных элементах типа A или хотя бы на достаточно большой выборке. Потому что и для вычитания isSemigroup(x, y, 0) выдаст true для любых x и y, хотя вычитание не ассоциативно.

Например, целые числа с операцией умножения являются полугруппой и удовлетворяют законам полугруппы:

given Semigroup[Int] = (x: Int, y: Int) => x * y
Semigroup[Int].combine(3, 5)
// val res0: Int = 15

Тут надо быть осторожным из-за переполнения типа, которое может нарушить законы класса типов, но проигнорируем здесь этот момент.

Итак, класс типов может характеризоваться не только своим определением, но ещё и набором законов.

Зачем нужны законы?

Поведение класса типов позволяет делать определенные выводы, на которых, как на "кирпичиках", можно построить нечто большее чем исходное описание.

Например, какой вывод можно сделать из закона "Замыкание"?

Если мы можем "сложить" два элемента определенного типа и получить в результате элемент того же типа, то это означает, что данную операцию можно проводить несколько раз над результатом предыдущего "сложения"! Т.е. из "сложения" мы можем получить "умножение", из "умножения" - "возведение в степень" и т.д..

И у нас уже есть не только 3 * 5, но и 3^5:

trait Semigroup[A]:
  def combine(x: A, y: A): A

  def combineN(x: A, n: Int :| Greater[0]): A =
    @tailrec def loop(b: A, k: Int, acc: A): A =
      if k == 1 then combine(b, acc)
      else
        val x = if (k & 1) == 1 then combine(b, acc) else acc
        loop(combine(b, b), k >>> 1, x)

    if n == 1 then x else loop(x, n - 1, x)
Semigroup[Int].combineN(3, 5)
// val res1: Int = 243

О том, что такое Int :| Greater[0] рассказывается в статье об уточняющих типах.

"Ассоциативность" говорит о том, что мы можем складывать элементы в любом порядке. А значит, можем использовать этот факт там, где он полезен - при реализации класса типов!

Например, как свернуть коллекцию из миллиона элементов? Разбить коллекцию пополам, параллельно свернуть каждую половину, а затем - "сложить" результаты?! Для Vector-а - да, но будет ли это эффективно для связанного списка?! Ответы на эти вопросы содержатся во многих книгах, но здесь важно другое - что мы можем выбирать, как "складывать" элементы коллекции, если это "сложение" ассоциативно.

Т.о. законы классов типов помогают наиболее эффективно реализовать эти классы типов.

Расширение подходящих типов

Поведение некоторых классов типов позволяет строить новые реализации на основе предыдущих. Например,

given nestedSemigroupInstance[A, B](using asm: Semigroup[A], bsm: Semigroup[B]): Semigroup[(A, B)] =
  (x: (A, B), y: (A, B)) => (asm.combine(x._1, y._1), bsm.combine(x._2, y._2))

Мы получили новую реализацию на основе существующих! Конечно, это возможно не всегда и зависит от класса типов, но все же...

Выводы

  • Классы типов задают определенное поведение.

  • Это поведение может задаваться как явно в коде, так и с помощью "соглашений", указанных в тестах, в документации или соблюдаемых "устно".

  • Поведение позволяет "строить выводы", расширяющие класс типов как в сторону получения новой функциональности, так и в сторону увеличения множества подходящих типов.

Итак, вернёмся к началу: является ли классом типов следующее определение?

trait Something[A, B]:
  def doSomething(x: A): B

Комментарии (12)


  1. GospodinKolhoznik
    04.11.2023 11:22
    +18

    Некоторое время назад каждый считал своим долгом объяснить всем вокруг что такое есть монада. Каждый объяснял так, как это понял и кто во что горазд. Знаковый пример это объяснение, что "монада это котёнок, завёрнутый в буррито". Сейчас людей вроде отпустило и мода на объяснения монад прошла. Вы же решили вместо монады объяснять, по-своему что такое тайпкласс. Очень непонятно написано. Представив себе, что я не знаю, что такое есть тайпклассы, я бы точно ничего не понял бы из этого объяснения.

    Зачем вообще нужна концепция тайпкласса? Какие проблемы он решает? Начать надо было с этого. Привести пример проблемы P. Потом привести несколько примеров тайпклассов начиная с самых распространенных, чтобы можно было пощупать на примерах что это такое. Потом показать как с помощью тайпклассов решается проблема P. И после этого уже можно дать формальное опеределение, а можно и не давать, от него всё равно никакого толку, слишком уж оно расплывчато сформулировано.


    1. auddu_k
      04.11.2023 11:22
      +4

      Подтверждаю - ничего не понятно ????‍♂️


  1. nikolz
    04.11.2023 11:22
    -7

    Полагаю, что термин "контракт" в данной статье неуместен, либо дайте свое(новое определение этому понятию.

    ----------------------

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

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



    1. alexxz
      04.11.2023 11:22
      +1

      Разные области могут иметь специфический смысл одного слова. Вы привели смысл в юридическом контексте. В программировании (и, возможно, ИТ) под контрактом обычно подразумевается формализованная договорённость сторон.


      1. nikolz
        04.11.2023 11:22
        -3

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

        При этом не имеет значение вид деятельности сторон договора - программирование или строительство или торговля борзыми щенками.

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

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


        1. dopusteam
          04.11.2023 11:22
          +4

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

          Вообще-то, нет.

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

          https://ru.wikipedia.org/wiki/Контрактное_программирование

          В объектно-ориентированном программировании контракт метода обычно включает следующую информацию:

          • возможные типы входных данных и их значение;

          • типы возвращаемых данных и их значение;

          • ...


          1. nikolz
            04.11.2023 11:22
            -2

            "контракт" и "контрактное программирование" - это два самостоятельных понятия, а не синонимы.

            Именно в правовом смысле, во втором понятии существительное - программирование - и это главное, а контактное - это прилагательное.

            Более того, это понятие относится к обязанностям людей, а не объектов языка как таковых.

            из вашей ссылки:

            Он предполагает, что проектировщик должен определить формальные, точные и верифицируемые спецификации интерфейсов для компонентов системы.


            1. dopusteam
              04.11.2023 11:22

              Я ж вам специально даже цитату привёл

              В объектно-ориентированном программировании контракт метода обычно включает следующую информацию

              Прочитайте целиком статью, а не куски из неё


              1. nikolz
                04.11.2023 11:22

                Вы сами-то читали из Вашей же ссылки:

                Данные спецификации называются «контрактами» в соответствии с концептуальной метафорой условий и ответственности в гражданско-правовых договорах.


                1. dopusteam
                  04.11.2023 11:22
                  +2

                  Вам слово метафора ни о чем не говорит? И процитированный мной выше дважды кусок

                  Но вообще я теряю суть диалога. Вы сказали, что контракт - это документ. Я привёл аргументы, что в ИТ термин используется в другом смысле. Вы не согласны?


                  1. nikolz
                    04.11.2023 11:22
                    -3

                    Мета́фора (др.-греч. μεταφορά «перенос; переносное значение», от μετά «над» + φορός «несущий») — слово или выражение, употребляемое в переносном значении, в основе которого лежит сравнение предмета или явления с каким-либо другим на основании их общего признака.

                    А Вам о чем говорит слово "метафора"?


  1. saipr
    04.11.2023 11:22
    -4

    Классы типов задают определенное поведение.

    Если бы от класса типа зависело поведение. Как раз наоборот - поведение определяет класс типа.