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


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


Тип — это то, над чем компьютер умеет рассуждать. Рассуждать в смысле привычной человеческой или формальной логики, то есть по правилам строить некоторые заключения из исходных посылок. Хорошая новость в том, что компьютер делает это автоматически. Бывают разные системы типов: одни упраздняют определённый вид ошибок, таких как несоответствие типов, утечка ресурсов (Rust Borrow Checking); другие автоматически генерируют реализацию (Haskell Type Class, Scala & Rust Trait). Когда типы легко воспринимается человеком, то служат документацией. Сильные системы типов могут за вас делать больше работы, чем слабые. А статическая обработка типов сделает эту работу раньше, при компиляции, а не позже, при выполнении, как динамическая.


Класс в ООП — это одна из систем типов, которая позволяет рассуждать о внутренней структуре, какие внутри есть поля и методы. Аналоги: запись, кортеж, тип-произведение.


Наследование в ООП смешивает как минимум 4 идеи, которые стоит реализовывать отдельными способами:


  1. Наследование данных, довольно бесполезное после того, как они обернуты в getter/setter и превратились в методы с наследованием реализации. А пример прозрачного оборачивания — Elm Records и Haskell Records.
  2. Наследование реализации для повторного использования, рекомендуется заменять на делегирование.
  3. Наследование интерфейса (Ad hoc полиморфизм) перегружает метод, то есть позволяет по одному имени получить разную реализацию для каждого типа. Аналоги: Java Interface, Scala & Rust Trait, Haskell Type Class; алгебра как тип данных и набор операций на этом типе.
  4. Объединение типов аналогично Java Enum, Scala Case Class, Elm & Haskell Algebraic Data Type, тип-сумма.

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


Объект в ООП — замыкание, где свободные переменные являются полями, желательно приватными.


ООП — популярный бренд, исторически сложившийся, но не уникальный набор идей без единого центрального стержня.


Полиморфизм бывает разным.


Типаж, интерфейс — тип-произведение, состоящее, в основном, из функций.


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


Visitor — шаблон проектирования, реализующий Pattern Matching.


Builder — шаблон проектирования, реализующий функцию высшего порядка, которая принимает части и выдаёт продукт.


Dependency Injection — шаблон проектирования, реализующий функцию высшего порядка, которая принимает зависимости и выдаёт продукт. Изоморфен шаблону Builder с поправкой на то, что зависимости, обычно, являются функциями.


Клиентская Web разработка — востребованный исторически сложившийся, но не уникальный набор идей, основанный на монопольном положении JavaScript.


MVC, MVP, MVVC — чистая функция и абстрактный автомат для обработки внешних событий.


Event Loop (Node.js, Rust tokio) — абстрактный автомат для обработки внешних событий.


Программирование — инженерная наука о композиция кода.


Теория категорий — фундаментальная теория о композиции чего угодно.


Изоморфизм — превращение или замена чего-либо во что-то другое и обратно, то есть суть изоморфных вещей одна.


Рефакторинг — изоморфизм, выполняемый людьми.


Оптимизация — изоморфизм, выполняемый компьютером.


(Чистая) функция — превращение одного в другое всегда одинаково.


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


Монада превращает один тип в другой, да так, что можно компоновать функции с побочным эффектом. Например, монада "ожидание" (Future, Promice) имеет побочным эффектом ожидание завершения длительной операции и преобразует тип "массив байт" в тип "ожидание массива байт", а тип "подтверждение" в тип "ожидание подтверждения". Передадим функции чтения имя файла и подождёт массив байт с диска. Затем передадим полученный массив байт функции отправки по сети и подождём, когда клиент подтвердит получение. А можно заменить пару ожиданий на одно составное (комбинатором (>>=), bind, and_then), которая сначала подождёт массив байт с диска, а потом подождёт подтверждения от клиента по сети, но главное, пропадёт явное промежуточное ожидание, позволив среде исполнения в это время заниматься другими полезными делами.


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


Вариантность говорит, что вывернутое на изнанку работает прямым (ковариантность) или вывернутым образом (контравариантность). Например, функции, которая ест тип яблоки или груши можно скормить яблоки, но нельзя тип яблоки или груши или персики потому, что персиками она подавится. Более всеядную функцию создать сложнее и их меньше. Больше аргумент — меньше функций с таким аргументом — это и есть "работает вывернутым образом", то есть функция контравариантна по аргументу. А вот по результату функция ковариантна.


Вычисления можно производить да хоть на камушках.



Квантовые вычисления работают не с битами, а с кубитами. Например, вместо шариков в Marble adding machine можно кидать Котов Шрёдингера. Например, пара котов даст 4 варианта: оба живых, первый живой, второй живой, нет выживших. В итоге за один запуск машины мы получим суммы всех возможных чисел. Проблема только в том, как превратить Котов Шрёдингера внизу машины в обычных котов, полезных в хозяйстве.


Зависимые типы — работа с типами и оптимизациями кода тем же способом (Pattern Matching и вычисления), что и со значениями. Зависимые типы позволяют переложить на компьютер работу с вычислимыми шаблонами и даже саму математику. Например, можно указать, что тип "множество элементов", когда элементов не более 64, можно заменить на тип u64, который поместится в регистр. Или компилятор может удостовериться, что размеры складываемых векторов одинаковы.


Дополнения и исправления принимаются.

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


  1. Hixon10
    15.12.2017 21:06

    Извините, в чём смысл вашей статьи?


    1. izzholtik
      15.12.2017 21:17
      +1

      Возможно, пригодится кому-то, кого заморозили в 1971, а сейчас выпустили.


      1. deb Автор
        16.12.2017 08:50

        Скажите это SQL из 1974 и реляционной алгебре, куда движутся распределённые хранилища, бывшие NoSQL, а сейчас NewSQL (Spanner, Cockroach DB). Или посмотрите на эволюцию Java, где добавили сахар для замыканий, анонимных функций, Stream API (оптимизация функтора). Тут аналогия с поколениями при сборке мусора: если до сих пор что-то помнят из 1971, то большие шансы, что ещё долго будут помнить. А свежий мусор из nursery забудется в следующем году.


    1. deb Автор
      16.12.2017 08:37

      В сети есть уйма холиваров, ООП vs ФП, языки со статической типизацией против динамической, зачем нужны компиляторы, которые мешают разрабатывать своими ошибками, когда можно покрыть юнит тестами, какой язык лучше. Если в них разобраться, то окажется что в одном варианте нужно делать ручками, а в другом за вас работу может сделать компьютер. Но, конечно, придётся потратить время, чтобы разобраться. Здесь указано, в какую сторону время тратить: в фундаментальные понятия, а не в популярные в нынешнем сезоне.


  1. yarosroman
    16.12.2017 06:41

    да, хорошая идея для статьи, копипаста различных цитат.


    1. deb Автор
      16.12.2017 08:58

      Приведите, пожалуйста, пруф-ссылку на копипасту :)
      Это скорее, awesome list.


  1. greabock
    16.12.2017 13:33

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


    1. deb Автор
      16.12.2017 14:37

      Цель материала была в том, чтобы человек, который раньше не встречал, что такое зависимые типы или обходил стороной слово «монада», начав с того, что ему знакомо, дочитал до конца, на этот раз понял, что штука-то в хозяйстве это полезная, и, может быть, глубже погрузился в тему, щёлкнув по ссылке.
      Если текст показался очевидным, то это вообще замечательно!


  1. lair
    16.12.2017 14:38

    … кому-нибудь будет интересен список ошибок в этих "определениях", или и так всем все понятно?


    1. AnutaU
      16.12.2017 14:46

      Мне интересен. Ваши комментарии — это как мастер-класс по занудству в лучшем смысле этого слова.


    1. deb Автор
      16.12.2017 14:46

      Вы правильно взяли «определения» в кавычки. Но ошибки, пожалуйста приведите. Как Маугли, воспитанный Википедией, я могу и ошибаться и рад бы был от вас чему-нибудь научиться.


  1. lair
    18.12.2017 14:39
    +1

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

    Даже не придираясь к утверждению "компьютер умеет рассуждать", здесь опущено важное: компьютер "умеет рассуждать" над данными. То, что вы здесь называете типом — всего лишь метаинформация о "хранилище" (будь то переменная или колонка в БД или впишите что угодно еще), в первую очередь — ограничивающая допустимый набор его значений.


    Хорошая новость в том, что компьютер делает это автоматически.

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


    Сильные системы типов могут за вас делать больше работы, чем слабые.

    Здесь забыт важный пункт: сильные системы типов могут требовать от вас больше работы, чем слабые. Кстати, эй, а что же такое "сильная" и "слабая" система типов?


    Класс в ООП — это одна из систем типов

    Класс — это не система типов. Класс — это (в некоторых ОО-языках) один из видов типов (простите); иными словами (в некоторых ОО-языках) всякий класс — это тип, но не всякий тип — это класс.


    Аналоги: запись, кортеж, тип-произведение.

    Нет. Если мы говорим о более-менее устоявшихся определениях, то запись, кортеж и типы-произведения — это не классы, и фундаментальное отличие состоит в том, что обычно класс предполагает поведение, а кортеж — нет.


    Наследование в ООП смешивает как минимум 4 идеи

    Ну вы бы хоть сказали, какое из определений наследования вы используете (и зачем вы это делаете).


    Наследование интерфейса (Ad hoc полиморфизм) перегружает метод, то есть позволяет по одному имени получить разную реализацию для каждого типа

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


    Объединение типов аналогично

    А тут я просто не понял, что вы хотели сказать, хотя я знаю, что такое типы-суммы. А представьте себе человека, который первый раз видит этот справочник и наследование вообще?


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

    Я как-то привык считать, что деконструирует деконструктор (C#), паттерн (F# и Haskell) и так далее. А паттерн-матчингом называется case expression, который на вход получает выражение, а дальше, используя разные варианты его деконструкции (или просто сравнения), выдает следующее выражение (или операцию).


    Объект в ООП — замыкание, где свободные переменные являются полями, желательно приватными.

    Спасибо, что предварительно объяснили, что такое "свободные переменные" и "замыкание". И это взгляд на объект с точки зрения функций, но не с точки зрения семантики — а семантика зачастую намного важнее. В частности, такой взгляд на объект не позволяет разделять объекты-сущности и объекты-значения.


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

    Спасибо, это даже не определения.


    Типаж, интерфейс — тип-произведение, состоящее, в основном, из функций.

    Ну во-первых, интерфейсы в ООП (таком, знаете, мейнстримном) совершенно не обязательно состоят из функций; собственно, там и функций-то нет. Во-вторых, я не уверен, что термин "тип-произведение" вообще можно применить к функциям: смотрите, у меня есть два интерфейса, Summable: float -> float и Multipliable: float -> float. У них функции одинакового типа (float -> float), и множество значений одинаковое. Но интерфейсы все равно разные, и их единственная операция имеет разную семантику. Куда семантика-то потерялась?


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

    Из этого тезиса можно сделать вывод, что уровни абстракции — это такая лестница, по которой можно идти вверх. Однако же нет, абстракции могут быть не иерархическими, а просто различными, и два языка одной степени "высокоуровневости" могут иметь разный набор абстракций (тем самым требуя разных паттернов).


    Visitor — шаблон проектирования, реализующий Pattern Matching.

    … за тем маленьким исключением, что для Visitor строение объекта не обязано быть публичным, а для Pattern Matching — обязано.


    Dependency Injection — шаблон проектирования, реализующий функцию высшего порядка, которая принимает зависимости и выдаёт продукт.

    … давайте начнем с простого, и выясним, что же такое "продукт"?


    Dependency Injection [...] изоморфен шаблону Builder с поправкой на то, что зависимости, обычно, являются функциями.

    Во-первых, вы еще не ввели понятие изоморфизма. Во-вторых, DI изоморфен Builder только по реализации, но не по семантике, а это очень важное различие. Впрочем, у вас везде изоморфизм про это забывает.


    MVC [...] чистая функция и абстрактный автомат для обработки внешних событий.

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


    Программирование — инженерная наука о композиция кода.

    Программирование — не наука (см. определение: "область человеческой деятельности, направленная на выработку и систематизацию объективных знаний о действительности"). Ну и непонятно, в каком значении вы тут употребляете слово "композиция".


    Изоморфизм — превращение или замена чего-либо во что-то другое и обратно, то есть суть изоморфных вещей одна.

    … и сейчас мы упремся в значение слова "суть". Смотрите:


    Рефакторинг — изоморфизм, выполняемый людьми.

    При рефакторинге сохраняется только внешнее поведение объекта под рефакторингом (причем, на самом деле, сохраняется оно только в рамках определенного контракта). Его внутренняя структура может измениться полностью. Его "нефункциональные характеристики" могут измениться полностью. Так что в зависимости от понимания слова "суть" это может быть как изоморфизм, так и нет.


    (собственно, рефакторинг не изоморфен, потому что рефакторинг предполагает неухудшение характеристик)


    Оптимизация — изоморфизм, выполняемый компьютером.

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


    (Чистая) функция — превращение одного в другое всегда одинаково

    Забыли про побочные эффекты.


    Функтор превращает один тип в другой (функция на типах), да так, что можно оптимизировать композицию чистых функций.

    Любая функция, которая "превращает один тип в другой (функция на типах), да так, что можно оптимизировать композицию чистых функций", является функтором?


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

    Какая связь между монадой Maybe и побочными эффектами? Или монадой "список"? (кстати, у вас список только что был функтором)