Поздравляем, вы щелкнули по статье, в названии которой есть «Алгебраические структуры». Я вижу, что тебя не так легко напугать, мой юный подмастерье. Я приветствую вас!

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

Добро пожаловать в седьмую часть «Функционального программирования для разработчиков JavaScript». Я очень нервничал, освещая эту тему. Просто потому, что некоторые определения так легко спутать. Итак, в качестве оговорки, я не математик. Я просто парень, который открыл для себя функциональное программирование и влюбился в него.

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

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

Функция идентичности

Для начала давайте взглянем на так называемую функцию идентификации. Держись крепче, это взорвет твой мозг. Готовый? Вот!

//    id :: a -> a
const id = x => x;
or 
function id (x) {
    return x;
}

Вы впечатлены? Нет?

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

Итак, функция идентификации принимает аргумент любого типа. И возвращает то же самое вещь. Нравится…

id (1); // 1
id ('hello'); // 'hello'
id (Just ([1, 2, 3])); // Just([1, 2, 3])

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

Что такое алгебраические структуры

Начнем с типа String. Вы, наверное, уже знаете, что с ним можно выполнять определенные операции. Вы можете concat это с другим String, вы можете проверить, является ли String равным другому String, и так далее. Это довольно простые вещи, правда? Алгебраические структуры дают нам общий язык «вещей», которые мы можем делать с нашими типами, и тем правилам, которым они следуют. Хорошо, давайте копнем глубже.

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

'a'  === 'a'  // true
 1   ===  1   // true
true === true // true
[1]  === [1]  // false

Хм ... похоже, что типArray Integer не способен обеспечить ожидаемый результат. Итак, равенство характерно не для всех типов. Хорошо, как мы можем это решить? Вот безумное предложение, а что, если мы создадим новую функцию?

ПРЕДУПРЕЖДЕНИЕ! Следующий код предназначен для демонстрационных целей. Не манипулируйте Array.prototype вот так. Библиотеки, построенные на спецификации Fantasy-Land, делают это совершенно иначе.

Теперь мы изменили встроенный тип Array, чтобы мы могли сравнивать два массива (на один уровень). Это настраивает нас на то, чтобы узнать название нашей первой алгебраической структуры Setoid. Тип формирует Setoid, когда реализует метод equals.

equals :: Setoid a => a ~> a -> Boolean

Я никогда не говорил тебе о ~>. Прочтите эту подпись следующим образом: a имеет метод equals, который принимает аргумент того же типа и возвращает Boolean.

Если бы мы реализовали Array.prototype.equals, как показано выше, в нашем коде, Array String, Array Integer и Array Boolean были бы примерами Setoids.

Стоит отметить, что определение методов для всех встроенных типов само по себе далеко от идеала (нам пришлось бы сделать то же самое для String, Integer и т. Д.). По этой причине настоятельно рекомендуется использовать стандартную библиотеку, такую ​​как Sanctuary. Он определит для вас equals методы для всех встроенных типов, не загрязняя prototype, как я сделал в приведенном выше примере. Конечно, это не ограничивается Setoids. Он также предоставит вам другие алгебраические структуры.

Почему полезно знать, что у нас есть Setoid? Ну, а пока давайте просто остановимся на этом - теперь у нас с вами может быть общий язык, когда мы смотрим на код проекта, в котором это реализовано. Я могу сказать вам, что, например, Array Integer - это Setoid, что означает, что вы можете запустить на нем метод equals, и, предоставив ему другой Array Integer, он гарантированно вернет вам Boolean.

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

Функтор

В статье о сигнатурах типов я кратко рассмотрел Functor. Но я не сообщил вам подробностей. Functor - это алгебраическая структура. Так что еще мы можем сказать о Functors? Прежде всего, Functor должен реализовывать метод map.

map :: Functor f => f a ~> (a -> b) -> f b

Functor a, имеет метод, который принимает функцию от a до b и возвращает Functor b.

Но чтобы быть Functor, необходимо применить еще два закона:

  1. Функциональный закон идентичности:
    Если мы предоставляем функцию Identity в качестве аргумента, функция map должна возвращать то же самое, например:
    [1, 2].map(x => x) // [1, 2]
  2. Закон композиции функторов:
    [1].map(f).map(g) === [1].map(x => g (f (x)))
    Означает, что сопоставление Functor с функцией f, а затем сопоставление с функцией g должно возвращать тот же результат, что и сопоставление Functor за один раз с x => g (f (x)). Это часто помогает нам находить более эффективные способы построения карт.

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

Алгебраические структуры и отношения

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

На этом изображении показаны различные алгебраические структуры, определенные в Спецификации Fantasy-Land. Надеюсь, теперь вы знаете, что каждый из них представляет собой какое-то поведение и законы. Теперь взгляните на дерево, начинающееся с Functor. В самом низу вы найдете другую алгебраическую структуру под названием Monad. Это изображение говорит вам, что Monad - это Applicative, который также является Apply, который, наконец, является Functor. Это должно сказать вам, что Monad - довольно мощная структура. Потому что мы знаем, что он следует правилам всего, что ниже его. Так что, если кто-то скажет вам, что тип - это Monad, у вас есть довольно большой набор инструментов, которые вы можете безопасно делать. Но сначала будет очень полезно узнать, как ведут себя указанные ниже типы.

Как узнать больше об алгебраических структурах

Есть некоторые ресурсы, на которые я хочу указать вам, чтобы вы могли больше узнать о различных структурах. Лучшим из известных мне ресурсов для глубокого погружения в алгебраические структуры, определенные в Fantasy-Land, является Серия статей Тома Хардинга. Прочтите это (читать не так уж много), и я обещаю, что вы будете иметь довольно хорошее представление о том, как все сочетается друг с другом.

Я также очень рекомендую четыре статьи Джеймса Синклера на тему Что я бы хотел, чтобы кто-то объяснил о функциональном программировании. Эта серия очень много сделала для моего понимания.

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

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

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

Мы очень приближаемся к той части, где мы увидим несколько примеров из реальной жизни. Так что следите за обновлениями! До новых встреч, удачи!