1. Первые шаги
2. Сочетаем функции
3. Частичное применение (каррирование)
4. Декларативное программирование
5. Бесточечная нотация
6. Неизменяемость и объекты
7. Неизменяемость и массивы
8. Линзы
9. Заключение


Данный пост — это восьмая часть серии статей о функциональном программировании под названием "Мышление в стиле Ramda".


В шестой и седьмой частях мы изучили, как читать, обновлять и трансформировать свойства объектов и элементы массивов в декларативном и иммутабельном стиле.


Ramda также предоставляет более общий инструмент для выполнения данных операций, который называется линзами.


Что ещё за линзы?


Линза объединяет функцию-"геттер" и функцию-"сеттер" в один механизм. Ramda предоставляет набор функций для работы с линзами.


Мы можем думать о линзах как о чём-то, что фокусируется на определённой части большой структуры данных.


Как я могу создать линзу?


Основной способ создания линзы в Ramda — это функция lens. lens принимает функцию-геттер и функцию-сеттер и возвращает новую линзу.


const person = {
  name: 'Randy',
  socialMedia: {
    github: 'randycoulman',
    twitter: '@randycoulman'
  }
}

const nameLens = lens(prop('name'), assoc('name'))
const twitterLens = lens(
  path(['socialMedia', 'twitter']),
  assocPath(['socialMedia', 'twitter'])
)

Здесь мы используем методы prop и path как наши функции-геттеры, а также assoc и assocPath как наши функции-сеттеры.


Обратите внимание, что мы продублировали аргументы с названием свойства и путём к нужному свойству для этих функций. К счастью, Ramda предоставляет классные сокращения для наиболее распространённых ситуаций использования линз: lensProp, lensPath и lensIndex.


  • lensProp создаёт линзу, которая фокусируется на свойстве объекта
  • lensPath создаёт линзу, которая фокусируется на вложенном свойстве объекта
  • lensIndex создаёт линзу, которая фокусируется на элементе массива

Мы можем переписать наши вышесозданные линзы, используя lensProp и lensPath:


const nameLens = lensProp('name')
const twitterLens = lensPath(['socialMedia', 'twitter'])

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


Что я могу делать со всем этим?


Окей, прекрасно, мы создали пару линз. Что мы теперь можем делать с ними?


Ramda предоставляет три функции для работы с линзами.


  • view читает значение линзы
  • set обновляет значение линзы
  • over применяет функцию трансформации к линзе

view(nameLens, person) // => 'Randy'

set(twitterLens, '@randy', person)
// => {
//   name: 'Randy',
//   socialMedia: {
//     github: 'randycoulman',
//     twitter: '@randy'
//   }
// }

over(nameLens, toUpper, person)
// => {
//   name: 'RANDY',
//   socialMedia: {
//     github: 'randycoulman',
//     twitter: '@randycoulman'
//   }
// }

Обратите внимание, что set и over возвращают весь объект с изменённым значением, на котором была сфокусирована ваша линза.


Заключение


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


Клиентский код далее может работать с нашими структурами данных через использование view, set и over без связки с точной формой структуры данных


Далее


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

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


  1. samsergey
    24.06.2018 23:40

    Основное свойство линз — замкнутости относительно композиции, то есть возможность использовать композицию линз, как линзы. Таким образом очень изящно обеспечивается доступ к вложенным структурам. Так ли это в Ramda? И как это работает?


    1. saggid Автор
      25.06.2018 00:04

      Я лично мало что понял из вашего сообщения, а как оно работает в рамде — описано в этой статье ) Большего я лично не знаю. Может кто-то более знающий придёт подскажет.


    1. RV170
      25.06.2018 01:15
      +1

      Да, роботает так же

      const firstIndexLens = R.lensIndex(0)
      const nameLens = R.lensProp('name')
      
      const firstPersonName = R.compose(firstIndexLens, nameLens)
      
      R.view(firstPersonName, [{ name: 'John' }, { name: 'Sam' }]) // "John"