Обновить

Категории типов. Часть 2. Функторы

Уровень сложностиСредний
Время на прочтение27 мин
Охват и читатели11K
Всего голосов 8: ↑8 и ↓0+9
Комментарии20

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

Наше понимание модели основано на понимании её частей и способов их составления. Поэтому отображение должно сохранять композируемость! Отображения, сохраняющие композируемость, называются функторами.

Я тоже считаю, что программирование должно основываться на моделировании реальных процессов. Только я не вижу причин считать, что главное это, переводя на программистский язык: «Разбиение монолита на микро сервисы». И, что мне с того, что при переносе старого кода, написанного на древнем языке, в новый код, с реализацией на новомодном языке, я сохраняю» композируемость» предыдущих микро сервисов? Может быть, там сама архитектура приложения ущербна?

В моем понимании, важны не «части» модели, а отношения между ними. Как известно, математика изучает не столько объекты, сколько отношения между ними. В абстрактном смысле, изоморфизм композируемости может быть интересен, но увидеть его смысл в практическом программировании, где это работает, при полном отсутствии скриншотов соответствующих программ, могут, как бы, «не только лишь все».

Чтобы осваивать теорию, нужна визуализация перспектив. Одно дело, когда вы рекламируете движок для создания игр, демонстрируя примеры готовых программ и, другое, когда вы просто говорите, что освоив его мы сможете что-то сделать . Как говориться: «Лучше один раз потрогать, чем сто раз увидеть».

Лично для меня, функциональное программирование на С++ это программирование без использования классов. Долгое время я их избегал, пока не наткнулся на хороший прототип, который решал мои задачи, с помощью классов. Попробовал – понравилось. С тех пор с удовольствием применяю их, в паре с фреймворком WTL. Сейчас, вот, избегаю классов на Питоне, но уже использую собственные модули там. Со временем, дозрею и до питоновских классов, когда увижу в них смысл :) .

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

Поэтому, резюмирую, если бы ваши статьи, каким-либо образом, объясняли, как правильно организовывать пользовательский интерфейс и программную логику на нем, допустим, с помощью вашей «теории категорий», то тогда бы был смысл ее осваивать. А так, приходится изобретать велосипеды, поскольку здесь даже ИИ мне слабо помогают.

P.S. Демонстрацию моего стиля программирования можно посмотреть в моей последней статье «Роль данных при изучении иностранного языка» – https://habr.com/ru/articles/930868/ .

В интернете и так хватает материала о применении на практике функторов/монад (характерных заимствований из теории категорий) - как они используются и на беке, и на фронте. Но сложно найти что-то про то, откуда эти абстракции берутся, почему они фундаментальны и неизбежны. Такая цель и обозначена в первой части данного обзора. Заодно попробуем выяснить, "какие типы можно" - почему в любых языках доступен практически один и тот же набор возможностей построения новых типов. Так же разберёмся с вопросами композируемости монад (почему из существующих сложно собрать композитную) и их потенциальными альтернативами.

Попутная цель - популяризация функционального программирования путём раскрытия его фундаментальности, надёжности и (сложно поверить!) простоты. Техники ФП и так уже повсеместно внедряются даже самые изначально ООП-шные и в доску императивные языки. Но об этом определённо стоит говорить почаще.

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

В интернете и так хватает материала о применении на практике функторов/монад (характерных заимствований из теории категорий) - как они используются и на беке, и на фронте.

Я так и знал, что про интерфейс (GUI) никто говорить не хочет. Про веб-программирование – материалов валом, а про правильное проектирование «графики» – почти ничего.

Но сложно найти что-то про то, откуда эти абстракции берутся, почему они фундаментальны и неизбежны. Такая цель и обозначена в первой части данного обзора. Заодно попробуем выяснить, "какие типы можно" - почему в любых языках доступен практически один и тот же набор возможностей построения новых типов. Так же разберёмся с вопросами композируемости монад (почему из существующих сложно собрать композитную) и их потенциальными альтернативами.

Это все хорошо и красиво, вопрос только один, а что мы будем иметь в итоге, кроме общего развития?

Попутная цель - популяризация функционального программирования путём раскрытия его фундаментальности, надёжности и (сложно поверить!) простоты. Техники ФП и так уже повсеместно внедряются даже самые изначально ООП-шные и в доску императивные языки. Но об этом определённо стоит говорить почаще.

Для меня этап ФП (как я его понимаю) пройден и уже не интересен. Вот о чём «определённо стоит говорить почаще», так это об WTL. Я собираюсь, но Миру и Граду надо предъявить Дары, то бишь, демонстрацию возможностей. Чтобы народ глянул на скриншот и сказал: «Во! Это мне интересно!» или «Это мне нах не нужно!». Главное, что окончательное решение будет принято в первые несколько секунд. Ведь мы же о программировании, в первую очередь, говорим, а не математическом самообразовании?

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

Интересно! Хотя я этот текст уже где-то встречал, уж очень знакомы многие словесные обороты. Однако, лично для себя я все эти советы свожу к одному методу «здравого смысла». Пока он помогал больше, чем все, мне известные.

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

Тема явно очень «горячая», 353 комментария на «эту небольшую заметку» – очень круто, говорю без иронии.

Только подобную заметку я могу написать и сам и даже что-то подобное уже писал, где-то в комментариях. Один из них (копирую текст, чтобы не переходить по ссылке) на https://habr.com/ru/companies/bothub/articles/812951/comments/#comment_26801667 :

«> Для наверное 95% программистских задач высшая математическая база особо и не нужна.

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

Тем не менее, считаю, что математика в программировании это больше, чем математика.

Лично я бы посоветовал вундеркинду, увлекающемуся программированием, закончить очно, после школы, мехмат МГУ, по специальности «математика». Затем получить распределение либо просто найти работу в крутом научно-производственном объединении (для мехматовца это несложно), специализирующемся в области высоких технологий (ИИ, оборона, Космос, экспериментальная физика, и т.д. и т.п.), после чего поступить во второй ВУЗ, с техническим уклоном, на специальность, связанной с работой. Лучше на дневное отделение, сразу на второй-третий курс, но можно и на вечернее. Тогда, наш «вундервунд» будет обречен на успех (в России, конечно).

Почему так?

Сужу по собственному опыту. Сам я учился «наоборот», пять лет дневного высшего образования в Политехническом институте, затем четыре года работы по распределению во Всесоюзном НИИ, затем пять лет очно на мехмате МГУ, по специальности «математика». Потом работа в крутом Научно-Производственном объединении. На успех был обречен, шеф Объединения, академик, с удовольствием взял меня к себе в аспиранты. Всё было идеально, если бы не распад СССР (который осуществили, чтобы не дать мне сделать карьеру, шутка, если что).

Что я могу сказать? Мехмат это очень круто! Это лучшая реализация для вундеркинда. Особенно повезло с преподавателями по матанализу, пожалуй, лучшими в мире. Как математик я мог бы найти работу где угодно, в области экспериментальной и теоретической физики, химии, любого НИИ. А программирование, по сравнению с математикой, казалось «детским лепетом на лужайке». Но, главное, математика развивает абстрактное мышление, а это очень способствует разработке и реализации сложных алгоритмов в программировании.

Зачем нужно второе высшее образование математику? Наши вундеркинды на мехмате были очень умными, каждый из них считал себя «пупом Земли», я им так и сказал, когда в общаге, на Ленинских горах, собралась группа наших первокурсников: «Среди вас семь вундеркиндов, и каждый считает, что весь мир существует ради него одного, любимого. Здесь только один я нормальный, правда, у меня уже есть высшее образование, но это не считается!».

Так вот, при всей их гениальности они после мехмата не знают жизни, ибо имели дело только со слишком абстрактной реальностью. Чтобы вернуть их на Землю, им надо поработать на производстве (пусть даже высокоинтеллектуальном и высокотехнологичном) и поучиться еще технической специальности. Ну и попрограммировать, ради практической пользы.

Да, этого всего у меня не было бы, если бы не было СССР. Сейчас жизнь другая, но принципы те же. Врожденные способности плюс желание учиться плюс фундаментальное образование плюс техническое образование плюс работа на благо страны – наше всё! Тогда на успех вы обречены! По крайней мере, я хотел бы повторить свой путь математика и программиста в следующей жизни.»

Поэтому, меня очень смущает ваша фраза:

Современная академическая математика отвратительна! Она нарочно дистанцируется от реального мира и сконцентрирована больше на самолюбовании. Совершенно абсурдный культ «царицы всех наук» и претензия на самостоятельную значимость пресекают большинство попыток познакомиться с ней поближе.

Ну, вообще-то, я не ожидал от вас такой сентенции. Вы просто не понимаете смысла абстрактной, теоретической математики. Она именно ДОЛЖНА «дистанцируется от реального мира» и отображать суть отношений между абстрактными объектами в чистом виде! Для работы с реальностью есть другая наука – физика. Все претензии к ней. Скажем, «Ландвшиц» (Ландау и Лифшиц, на жаргоне), в своем уникальном десятитомнике теоретической физики позволяли себе математические преобразования, от которых математики лишь тихо вздыхали. Но, ведь Ландау – Гений же! Он, просто, нашел, тот промежуточный уровень абстракции, когда и математики не могли слишком уж сильно «наехать» и связь с реальностью не терять.

Поэтому реальность для математиков это, всего лишь, повод для абстракций. Их задачи увидеть новые отношения (в «чистом» виде) и теоретически изучить их. А про прикладные вопросы – пусть думают другие! И это абсолютно правильно!

что мы будем иметь в итоге, кроме общего развития?

Ведь мы же о программировании, в первую очередь, говорим, а не математическом самообразовании?

Не стоит недооценивать расширение кругозора. Поднимаясь выше, мы расширяем горизонты возможностей, находим лучшие пути решения практических задач. Систематическое математическое (вообще любое!) образование - это очень круто, но не для всех это приемлемый вариант. И даже в этом случае могут быть полезны популярные адаптации материала.

Вы просто не понимаете смысла абстрактной, теоретической математики. Она именно ДОЛЖНА «дистанцируется от реального мира»

Какой смысл у "смысла математики" и кому и чего она "должна" - это очень дискуссионные вопросы. Но не думаю что это стоит обсуждать в комментариях под данной статьёй)).

Так как я со scala постоянно не работаю, то тут я завис на 30 минут, разбираясь в синтаксисе scala "type Category[==>[F[_], G[_]]] = (

identity: [F[_]] => () => F ==> F, compose : [F[_], G[_], H[_]] => (G ==> H, F ==> G) => F ==> H)

Этот коментарий меня запутал "Параметризация стрелкой ещё пригодится нам в следующей части обзора. Сейчас же эта стрелка будет такой: "

Как минимум здесь надо изменить "Параметризация type Category '==>' ("стрелкой") ещё пригодится нам в следующей части обзора."

Или дать еще большое информации "Здесь ==> не встроенная стрелка =>, а ‘имя’ для параметра у типа Category. в Scala 3 любой типовой конструктор с двумя параметрами можно записывать инфиксно: A op B является синтаксическим сахаром для op[A, B]. поэтому мы тоже можем писать F ==> F или G ==> ."

По моему здесь "е" это опечатка "

val fOpt: Option[Int] => Option[String] = .map("строка " + е )"

Здесь по моему тоже опечатка и надо fOpt "val gLst: List [Int] => List [String] = fopt.fmap[List]"

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

Здесь поменяем fab на fa_fb " extension [F[_], A, B](fa_fb: F[A] => F[B]) def fmap[G[_]](using functor: Hom[F, G]): G[A] => G[B] = functor(fa_fb)

здесь поменяем fOpt тоже на fa_fb " [A, B] => (fa_fb: Option[A] => Option[B]) => (listA: List[A]) => fa_fb(listA.headOption).map(_ +: functorOptLst(fa_fb)(listA.tail)).toList.flatten"

и возможно val:fOpt_fOpt будет тоже лучше чем val:fOpt

Я попробую придумать пример лучше. "без читинга". Если получится, то я напишу.

Еще раз спасибо за Ваш статью.

Некоторый "рефакторинг" делался уже после вставки фрагментов кода из IDE в статью - отсюда подобные косяки. Поэтому такие правки в статье лучше избегать)
Другие же правки внёс, спасибо.

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

Мне кажется здесь коментарий не к той строке.

type Pair[X] = X × X

type Pow2[X] = Boolean => X // X²

К той. Цикл моих обзоров посвящён так или иначе теории типов. В предыдущих не раз показывалось, что одна из фундаментальных операций над типами - это экспоненциал B^A, который обычно записывается через стрелочку A => B (тип функции). Повторять элементарные вещи в каждой статье было бы неудачным решением. Отчасти по этому материал разбиваю на отдельные обзоры.

Спасибо, это уже больше информации. Если возможно добавьте пожалуйста в этом месте ссылку на ваш обзор про типы. Спасибо.

Вы прекрасно нарисовали диаграмму для функтора. Далее вы пишите

"type Hom[Hom1[_, ], Hom2[, _]] = [A, B] => Hom1[A, B] => Hom2[A, B]"

Почему не взять обозначение из диаграммы и написать

"type Funct[MorfC[_, ], MorfD[, _]] = [A, B] => MorfC[A, B] => MorfD[A, B]"

в вашем варианте на самом деле получется "пенопластом по стеклу"

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

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

Как новичок в scala я здесь реально запутался:

extension [A, B](f: A => B)
  inline def lift[F[_]: Functor as functor]: F[A] => F[B] =
    functor(f)

extension[F[_]: Functor as functor, A] (fa: F[A])
  inline def map[B](f: A => B): F[B] =
    f.lift[F](fa)

шикарный конечно у scala синтаксис, но чтобы понять, что F[_] это параметра типа Functor, а не обозначение типа Functor, у меня ушло довольно много времени.

Возможно так было бы лучше, но я не эксперт в scala:

  extension [A, B](f: A => B)
    inline def lift[F[_]](using functor: Functor[F]): F[A] => F[B] =
      functor(f)
  
  extension [F[_], A](fa: F[A])
    inline def map[B](f: A => B)(using functor: Functor[F]): F[B] =
      f.lift[F](fa)

Так записать действительно можно.
Но обозначение F[_]: Functor стандартно для Scala. В целом я ожидал, что читатель будет знаком со Scala. Если ограничиваться только самым простым синтаксисом, код станет более громоздким и трудночитаемым.
И да, Functor в данном случае - это не "тип", а класс типа. такой синтаксис ограничивает использование функции только с такими F[_]для которых в контексте вызова существует экземпляр Functor[F] - он подтягивается при вызове и может использоваться внутри (а может и нет))

Я что то совсем поплыл в "Константный функтор". Вроде все слова понятны, но картинка не складывается. Если возможно дайте один конкретный пример. Спасибо.

Да тут можно запутаться, и похоже, в моём изложении этот момент не стал яснее...

Функтор \Delta- диагоналный. Он отображает объекты категории C "на диагональ" декартового произведения категорий C \times C \times \ldotsТакую категорию также можно рассматривать как категорию функторов C^I. Когда мы применяем функтор \Delta к объекту c: C мы получаем константный функтор \Delta c. Он отображает (дискретные) объекты индексной категории I в один и тот же объект c: C.

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

Публикации