Ramda.js - это функциональная служебная библиотека JavaScript. Он похож на более известные библиотеки, такие как LoDash или Underscore. Ramda отличается более сильным упором на функциональный стиль программирования.

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

Этот пост не предназначен для введения в функциональное программирование. Для этого я бы порекомендовал курс Мартина Одерского Принципы функционального программирования в Scala. Если вы предпочитаете книги (как и я), Структура и интерпретация компьютерных программ прекрасна (если требует). Этот пост также не предназначен для ознакомления с Ramda.js (для чего см. Серия Рэнди Кулмана.

Ramda оказалась особенно полезной в нашем веб-приложении для спортивной науки PyCoach. PyCoach опрашивает спортсменов на предмет наличия определенных маркеров, предназначенных для определения риска травмы, и отображает эти данные тренеру или тренеру. Вот - случайно сгенерированный пример. Этот проект требует преобразования данных обследования, и Ramda идеально подходит для этой работы.

Каррирование в Рамде

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

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

Пример: получение индивидуального счета игрока

Возьмем реальный пример из проекта PyCoach. Используя PyCoach, спортсмен будет заполнять ежедневную анкету о состоянии здоровья. В анкете спортсменов просят оценить свое самочувствие по шкале от 0 до 6 (чем выше, тем лучше), по таким параметрам, как болезненность, сон или стресс.

Проект PyCoach использует Angular 1.5 на передней панели. Сервис(wellnessQuestionnaireService) получает массив всех спортсменов в команде и их ответы на анкету. Эти данные хранятся на главном контроллере как ctrl.playerData.

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

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

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

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

Далее у нас есть getAllData. Здесь используется функция Ramda, filter. filter принимает функцию, которая возвращает логическое значение и массив. Каждый элемент аргумента массива передается аргументу функции. Все элементы массива, для которых функция возвращает истину, возвращаются в новом массиве. В этом случае нас интересует только один объект данных спортсмена, и мы выбираем его на основе атрибута itspk.

Обратите внимание, однако, что, хотя мы передали filter функцию, мы не передали ей массив. Поскольку Ramda автоматически выполняет картирование своих функций, это означает, что возвращаемое значение getAllData не является объектом спортсмена: это другая функция, ожидающая массива. Как только массив передается в getAllData, getAllData применяет к нему функцию, которую мы уже дали ему. Мы собираемся дождаться Ramda.pipe, чтобы передать данные. Подробнее об этом через минуту.

getArrayForDataType использует Ramda.prop. Метод prop просто возвращает значение для данного ключа в объекте. В данном случае это будет массив ответов игрока на определенный показатель (например, болезненность). Первый параметр prop - это ключ, а второй - объект, из которого мы хотим вернуть значение. Обратите внимание, что мы не предоставили ему объект, а только ключ, который нам нужен (например, soreness_data).

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

Для простоты будем рассматривать getIndividualScoreForDate как черный ящик. Мы можем это сделать, если знаем, что это чистая функция, потому что чистые функции ссылочно прозрачны. Другими словами, мы можем просто заменить возвращаемое значение чистой функции вместо вызова функции, не изменяя ничего другого в состоянии нашей программы. Все, что нам нужно знать о getIndividualScoreForDate, - это то, что это каррированная функция с двумя параметрами: во-первых, массив объектов оценки для определенного показателя (например, болезненность) и, во-вторых, дата. У нас еще нет массива оценок для предоставления (помните: предыдущие константы возвращали функции, ожидающие еще одного аргумента). Но мы знаем дату, для которой мы хотим получить оценку, потому что она предоставляется в качестве аргумента для ctrl.getIndividualScore.. Мы хотим использовать функцию каррирования, но мы хотим передать второй аргумент, а не первый.

К счастью, нам помогает flip метод Рамды. flip принимает функцию и возвращает эту функцию с обратными параметрами. Это позволяет нам передать дату, которую мы хотим. getScoreForDate ссылается на функцию, которая возвращает результат атлета за любую дату, которую мы ввели.

До этого момента мы не ссылались на массив ctrl.playerData, несмотря на то, что каждая созданная нами функция ссылается на данные в этом массиве. Каждая из трех функций - getAllData, getArrayForDataType, getScoreForDate - ожидает одного аргумента и возвращает один результат:

getAllData

  • Вход: ожидание массива всех ответов спортсмена (ctrl.playerData)
  • Вывод: возвращает один объект из массива ctrl.playerData для спортсмена, который нас интересует.

getArrayForDataType

  • Входные данные: берет объект из массива `ctrl.playerData` для спортсмена, который нас интересует.
  • Вывод: возвращает значение массива в объекте спортсмена, ключ которого, например, «sleep_data».

getScoreForDate

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

Вы заметите, что эти три функции могут быть добавлены в конец: то, что выходит из getAllData, является именно тем, что getArrayForDataType принимает в качестве входных данных. И то, что выходит из getArrayForDataType, - это именно то, что getScoreForDate принимает в качестве аргумента. Если мы просто объединим эти три функции и введем ctrl.playerData в верхнюю часть, наша оценка для конкретного спортсмена в конкретный день должна упасть ниже.

Объединение таких функций называется функциональной композицией. Метод Ramda pipe позволяет нам создавать функции. pipe возвращает функцию, которая передает любые аргументы, которые она передала функциям, начиная с первого. Результат каждой функции, в свою очередь, передается в последующую функцию или, если последующей функции нет, просто возвращается. score - это результат, который мы искали. Поскольку все функции, переданные в качестве аргумента pipe, являются чистыми функциями, нам не нужно беспокоиться о неожиданных побочных эффектах.

Эти концептуальные элементы - каррированные функции, чистые функции и функциональная композиция - в сочетании с подходом Ramda к последним данным, упрощают нашу работу. Ramda также является хорошим входом в более чистые формы функционального программирования, такие как Lisp, PureScript или Haskell.

Если у вас есть вопросы, напишите мне в Twitter. Наконец, спасибо Мэтту Смиту за то, что познакомил нас с Рамдой!