В этой статье мы поговорим о функторах. О функторах из библиотеки Cats, а не о классических функторах, которые мы все знаем и любим. Рассмотрим контравариантные функторы (Contravariant Functors), представленные в Cats в виде тайпкласса Contravariant. Некоторые из вас, возможно, не знают, что классический функтор (Functor) с операцией map, который мы ежедневно используем в наших Scala Cats-проектах, на самом деле является ковариантным функтором (Covariant Functor). Также хочу отметить, что термин "Вариантность" (Variance) применительно к функторам не имеет ничего общего с различными видами вариативности, которые мы знаем, когда речь идет о типах и параметрическом полиморфизме. Типичный функтор в терминах функционального программирования Scala представляет собой тайпкласс, оперирующий типами высших порядков (higher-kinded type), что оказывается весьма полезным, когда мы хотим абстрагироваться и обобщить наши API. Для полноты картины, поскольку мы не будем говорить классических функторах, давайте посмотрим на простой пример: Обобщенная версия reverse не зависит от типа обертки, которую мы хотим использовать, если в области видимости есть экземпляр Functor. В Cats это обычно происходит при импорте, например, Option: Итак, я уверен, что вы все это уже знали. Функторы повсюду, и мы часто их используем. Аппликативы (Applicative) — это функторы, монады (Monad) — это функторы, даже простая функция с одним параметром (Function1) — это тоже функтор. Суть функтора (Functor) в методе map, преобразующим обернутое значение типа A в тип B с сохранением обертки. Но хотите верьте, хотите нет, бывают случаи, когда мы хотим поменять местами типы в функции Functor f, чтобы она принимала тип B и возвращала тип A, но сохранила возвращаемую обертку для типа B — запутались? Если да, то читайте дальше — эта статья как раз для вас :). Представьте себе простой тайпкласс, преобразующий некоторый тип T в Boolean. Определим его как простой трейт Filter: Для работы с нашим тайпклассом создадим очень простой экземпляр и интерфейс: Извините, что я не придумал здесь ничего более умного :) — реализация здесь не важна. Конечно, первый println выведет false, а второй — true. Теперь представьте, что вам нужна функциональность функтора для тайпкласса Filter, чтобы преобразовать его из Filter[String] в Filter[Int] с помощью метода map: Видите ли вы проблему, с которой мы здесь столкнулись? Мы не можем передать значение в функцию f, поскольку нельзя использовать тип B в качестве входных данных для функции f. Здесь нам нужен тип A. Другими словами, мы хотим получить A => Boolean из B => Boolean, имея функцию A => B. Как это сделать? Нужно использовать контравариантный функтор вместо ковариантного. В Cats тайпкласс, предназначенный для этого, называется просто Contravariant. Как вы, вероятно, заметили, тип входного параметра и тип выходного поменялись местами: Композиция с контравариантным функтором (Contravariant) так же проста, как и с обычным ковариантным: Аналогично мы можем использовать Contravariant для работы с обернутыми значениями, например, в Option. Типичный пример с Show[_] из Cats: или более кратко: Таким образом, для фильтров мы можем использовать любые типы, обернутые в Option: И избавиться от _.get: Это все, что касается контравариантных функторов. Надеюсь, что эта тема стала вам более понятна и вы добавили еще один тайпкласс в свой инструментарий. Конечно, в нашей любимой библиотеке Cats есть множество других интересных тайпклассов и даже еще один интересный Functor :), но это я оставлю на потом. Материал подготовлен в рамках курса "Scala-разработчик".  Всех желающих приглашаем на открытый урок «Разработка простого REST API c помощью HTTP4S и ZIO». На примере построения простого веб сервиса с REST API, разберем основные компоненты (пути, бизнес логика, доступ к данным, документация), а также посмотрим как дружат такие функциональные библиотеки, как http4s, cats, zio в рамках одного приложения. РЕГИСТРАЦИЯ Редактор