Перевод терминов функционального программирования

Материал из ТХАБ.РФ
Версия от 10:51, 3 сентября 2020; Админ (обсуждение | вклад) (из статьи адаптированный текст)

Перейти к: навигация, поиск

Термины должны выбираться такими чтобы не требовать объяснения. Необходимо обращать внимание на то как называют такие понятия опытные программисты когда объясняют "на пальцах".

  • монада - list - контейнер
  • map() - преобразовать
  • функтор - обработчик

Рабочие варианты терминов

  • list - Список, контейнер
  • map - Прим (применить функцию), преобразовать
  • car - ПЭЛ (первый элемент), первый
  • cdr - ОЭЛ (остальные элементы), остаток

из статьи адаптированный текст

Теперь наконец попробуем разобраться в терминах Теории Категорий на понятных условному сишнику/джависту примерах.

Категориятип данных - любой примитивный или составной: строка, число, пара строка-число (кортеж), массив чисел, тип функций (например, функция IntToStr имеет тип Целый -> Строка). Функциональные типы (т.е. сигнатуры) – полноценные типы. Можно из них тоже собрать кортеж или сложить в массив. Параметры обобщенных типов (те, которые с дженериками, т.е. в Array[Int], например, Array – это обобщённый тип, а Int – это его параметр) еще могут быть Ковариантными/Контравариантными/Инвариантными. Эта тема стоит отдельной статьи.

Важное уточнение: данное тут определение очень вольное. Категория — понятие еще более абстрактное, чем тип. Но для начала понимания, давайте примем пока это упрощение.

Морфизмпреобразователь типов - это любая функция, преобразующая один тип в другой. Преобразователь типов IntToStr – это морфизм. Итого: видим «морфизм» — читаем «функция конвертации». «Эндоморфизм» — это преобразователь типов (морфизм) внутри типа данных (категории), т.е. преобразование типа в самого себя. Функция «синус» - Эндоморфизм из Типа Данных (Категории) Double в нее же, хотя и крайне примитивный. Более сложный пример преобразователь типов (морфизма): преобразователь пары строк (username, password) в объект сессии.

Монада – это простой контейнер. Ее основная цель – обрабатывать данные в контейнере, не вынимая их наружу. Для этого к ней прицепили парочку функций Преобразовать (map). Например, если у нас есть монада-список (массив) чисел, то преобразование их в строки можно сделать прямо в массиве, сразу получив на выходе готовый массив строк, не заморачиваясь с циклами, созданием новых массивов и т.п.

Важное уточнение: когда я говорю «превратили числа в строки, не доставая из контейнера», я не имею в виду, что поменялось содержимое самого массива. Исходный массив (экземпляр) остается неизменным, но вызвав преобразование, мы получим второй массив (или дерево, или любой другой контейнер) идентичной структуры, только уже содержащий строки.

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

В хаскеле функция такой обработки называется bind (>>=), что имеет корни в Теории Категорий. Ведь bind – это «связывание», т.е. функция bind фактически создает ребро в графе категорий (связывает узлы). В большинстве языков эта функция называется map() (строго говоря, flatMap) («отобразить», «поставить в соответствие»). По мне, логичнее было бы ее назвать cast() («снять слепок», «преобразовать»), но меня почему-то не спросили.

Есть распространённая монада Option/Maybe, смысл которой в том, чтобы хранить одно единственное значение. Или не хранить. Например, мы могли бы сделать функцию StrToIntOption, которая бы принимала строку и возвращала Option[Int], т.е. такой Контейнер (монаду), в которой либо лежало бы число (если строка в него преобразуется (парсится)), либо не содержало бы ничего. С таким контейнером мы можем делать разные вещи, даже не проверяя, что в нем лежит. Например, можем умножить его содержимое на «2», взять синус, вывести на экран или отправить по сети. Для этого мы используем наш метод Преобразовать (map()), передав в него в качестве параметра функцию, которая должна сделать что-то полезное. Но фактически выполнена эта функция будет только, если в контейнере значение правда лежит (число преобразовалось (распарсилось)). Если в контейнере ничего нет, то ничего и не произойдет, ничего не умножится, ничего не отправится.

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

Функтор — это та самая упомянутая выше функция map/bind. Смысл названия «Функтор»: «функция над функциями», но он не отражает ее сути. Суть же – взять какой-нибудь преобразователь типов (морфизм) и применить его прямо внутри контейнера (монады). Т.е. Функтор – это преобразование (морфизм) в контейнере (монаде). Функтор выглядит со стороны это как будто вызвали функцию преобразовать ("map"), передав в качестве параметра другую функцию - преобразователь типов (ЦелоеВСтроку), а в результате она вернет нам такой же массив, только уже со строками вместо чисел.

Ссылки