Когда я начал писать на Scala, я слышал, как кто-то сказал, что List, Option, Future и т. Д. - это монады и функторы. Я хотел понять, что такое монада или функтор.

Быстрый поиск в Google, и я получил статьи, объясняющие это

Шутки в сторону?

Теперь я расскажу вам вымышленную историю, которая может помочь понять, что такое монада или функтор.

Жил-был супермаркет Скала. Я пошел туда, чтобы купить 1 кг арахиса.

Я взял 1 кг арахиса, но я искал что-нибудь, чтобы их нести, потому что, очевидно, я не мог нести их свободно.

"Я могу вам чем-нибудь помочь?" - спросил меня джентльмен.

«Я хочу завернуть этот арахис в пакет». Я сказал.

"Конечно. Просто дайте мне арахис, и я заверну их в пакет.

«Дайте мне что-нибудь, и я передам вам это завернутым в пакет. Это моя работа." - Единица

"Как твое имя?"

«Люди называют меня Unit здесь, в магазине Scala. В магазине Haskell меня называют вернуться, - улыбнулся он.

Я также попросил его положить 3 пакета сахара по 1 кг каждый (для моих друзей). Тут я вдруг вспомнил, что они просили полкг сахара. Я попросил Unit сделать половину.

«Простите, сэр, я не могу этого сделать. Все, что я знаю, это как заворачивать вещи в сумку. Я не могу выполнять какие-либо операции с тем, что находится внутри ».

Я был опечален тем фактом, что мне приходится распаковывать пакеты, чтобы они делались наполовину, а затем возвращать их в подразделение для повторной упаковки. Слишком много работы, чтобы приготовить сахар наполовину. Я хотел, чтобы кто-нибудь мог мне помочь.

"Я могу вам чем-нибудь помочь?" Пришел еще один джентльмен с улыбкой. Он распаковал каждую сумку, позвонил парню, который умеет делать половинки сахара, а затем снова запаковал ее в сумку и передал мне.

"Теперь, как тебя зовут?" Я спросил его с благодарностью.

«карта, люди называют меня карта. Люди в магазине Haskell добавляют дополнительный f перед моим именем. Меня зовут fmap. Смешные люди."

Я могу заменить мешок сахара мешком с арахисом. Я могу дать пакет жареного арахиса, если вы дадите мне пакет арахиса.

«Скажите мне сделать что-нибудь с тем, что находится внутри сумки, и я сделаю это, сначала распакую сумку, а затем снова упакую ее, добавив в нее что-то новое». - карта

Функтор

Сумка вместе со мной / map называется Functor. По сути, сумка известна как функтор, когда рядом есть карта.

Процесс использования функции карты в Functor очень хорошо объясняется следующей иллюстрацией, взятой из: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

Боже мой, так это называется функтором, который я пытался изучить с давних пор. Слезы счастья катились по моему лицу.

Я достал ноутбук и начал писать Bag Functor. Вот.

В нашей истории:

  • Сумка - это Функтор
  • map - это функция карты на Functor
  • половина - функция, делающая сахар наполовину
  • сахар / арахис - это общий тип A или тип содержимого внутри Functor
case class Bag[A](content: A) {
 def map[B](f: A => B): Bag[B] = Bag(f(content))
}

case class Sugar(weight: Double)
// the guy who is expert at making sugar half
def half = (sugar: Sugar) => Sugar(sugar.weight / 2)
val sugarBag: Bag[Sugar] = Bag(Sugar(1)) //Bag functor of type sugar
// map is the guy in our story who can perform operations 
// by unwrapping the bag and then calling respective function
// and wraps the content back in a bag
val halfSugarBag: Bag[Sugar] = sugarBag.map(sugar => half(sugar))

Где это единица в приведенном выше коде. Модуль в приведенном выше коде - это метод apply, предоставляемый классом case, и поэтому он не отображается. Метод Apply принимает содержимое и возвращает пакет содержимого.

def apply(content: A): Bag[A] = new Bag(content)

Я был так счастлив, что понял концепцию Functor.

«Но, сэр, нас называют Functor, только если мы следуем определенным законам». Сказал карту. Теперь я мало волновался и внимательно слушал.

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

Если вы дадите мне 1 кг сахара, я дам вам 1 кг сахара. Дай мне все, что угодно, я тебе верну - Личность

def identity[A](x: A): A = x

1. Закон о личности

Когда map вызывается в Functor с функцией идентификации, вы получаете Functor обратно.

Functor[X].map(x => identity(x)) == Functor[X]
or
Functor[X].map(identity) == Functor[X]

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

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

Я начал проверять закон:

val sugarBag = Bag(Sugar(1))
sugarBag.map(identity) === sugarBag

2. ассоциативный закон

Пусть f и g - функции, которые мы хотим применить к значению, содержащемуся в функторе. Затем вызов map с f, а затем map с g эквивалентен вызову map с g, составленным с f (g после f или g (f (x))).

Если вы дадите мне пакет с арахисом весом 1 кг и попросите его обжарить его, я дам вам пакет жареного арахиса. Если вы попросите меня посолить пакет с жареным арахисом, я снова разверну пакет с жареным арахисом, посолу его и верну вам.

Эта операция эквивалентна тому, что я разворачиваю пакет с арахисом, жарю его, затем солю, а затем снова упаковываю.

- карта

Functor[X].map(f).map(g) == Functor[X].map(x => g(f(x))

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

Примеры функторов в Scala

  • Вариант
  • Список
  • Будущее
  • Пытаться
  • Или

В чем преимущество функторов

Возьмем пример функтора Option.

Опция возвращает значение None или Some. т.е. контейнер может содержать или не содержать значение.

Допустим, мы хотим добавить значение Int к Option of Int, а затем умножить его на другое число. Без метода карты реализация будет примерно такой:

val x: Option[Int] = Some(1)
val y: Int = 2
val m: Int = 2
val z = if(x.isDefined) Some((x.get + y) * m) else None

Метод map для функтора упрощает эту логику.

val x: Option[Int] = Some(1)
val y: Int = 2
val m: Int = 2
val z = x.map(a => (a+y) * m)
//or with the help of associative law 
val z = x
  .map(_ + y)
  .map(_ * m)

Монада

Я был доволен функторами, поскольку они действительно помогали мне всякий раз, когда я посещал супермаркет Scala. Однажды я зашел в супермаркет и купил пакет 1 кг сахара. Потом я понял, что мне нужно 2 кг сахара. Я обратился к карте с просьбой о помощи. карта развернула сахар и попросила парня удвоить сахар. Парень, который был экспертом по удвоению сахара, был чересчур умен. Он не только удвоил сахар, но и упаковал его в пакет и передал его карте. карта аккуратно упаковала все, что было дано экспертом, и передала мне сумку.

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

«Зачем ты упаковал пакет в другой, если сахар уже был упакован?» - спросил я у карты.

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

Я спросил его, есть ли кто-нибудь, кто может сгладить это для меня? Я просто хочу одну упаковку.

«flatten может вам помочь». Он позвонил другому джентльмену по имени Flatten.

«Дайте мне пакет с двойной оберткой, и я его расплющу. Я дам тебе сахар только в одном пакете »- сплющить

«Дайте мне вещи, дважды завернутые в сумку, и я верну вам то же самое, но в одинарной упаковке» - сплющить

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

Вы помните парня из подразделения? Сумка с блоком, картой и разверткой превращается в монаду.

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

«unit, map, flatten, ребята, круто, но было бы удобно, если бы есть кто-то, кто знает, как развернуть / развернуть и сгладить и то, и другое. Кто-то, кто может сделать работу по картированию и выравниванию, - сказал я.

«Ты обо мне говоришь?» Я услышал голос.

«Меня зовут flatMap, и я делаю именно это. Я могу сделать карту и сгладить и то, и другое ».

Сумка с unit и flatMap превращается в Monad.

Пришло время достать ноутбук и написать самую первую монаду. Я уже создал функтор Bag. Я мог бы просто расширить тот же функтор, чтобы он стал монадой, добавив flatMap или flatten.

case class Bag[A](content: A) {
  def map[B](f: A => B): Bag[B] = Bag(f(content))

  def flatMap[B](f: A => Bag[B]): Bag[B] = f(content)

  def flatten = content

}
def double = (sugar: Sugar) => Bag(Sugar(sugar.weight * 2))
val doubleSugarBag = sugarBag.map(sugar => double(sugar)).flatten
or
val doubleSugarBag = sugarBag.flatMap(sugar => double(sugar))

Да, мы похожи на Functor, но мы также знаем, как сгладить двойной перенос. «Вы должны следовать определенным законам, например, функторам?» Я спросил.

«Да, мы должны подчиняться следующим законам, чтобы называть себя Монадой»

1. Закон левой идентичности

unit(x).flatMap(f) == f(x)

i.e.

Bag(Sugar(1)).flatMap(double) == double(Sugar(1))
//or to be more explicit the same statement could be written as
Bag.apply(Sugar(1)).flatMap(z => double(z)) == double(Sugar(1))

Здесь

  • unit - это применить функцию монады.
  • f - это двойная функция, которая удваивает количество
  • x - 1 кг сахара, т. е. сахар (1)

Итак, Монада Багов выполняет первый закон. Ура.

2. Закон о праве на идентичность

Monad[X].flatMap(unit) == Monad[X]

i.e.

Bag(Sugar(1)).flatMap(Bag.apply) == Bag(Sugar(1))

Баг-монада также выполняет второй закон.

3. Закон ассоциативности

m.flatMap(f).flatMap(g) == m.flatMap(x ⇒ f(x).flatMap(g))

i.e.

Bag(Sugar(1)).flatMap(double).flatMap(tripple) == Bag(Sugar(1)).flatMap(x ⇒ double(x).flatMap(tripple))

Итак, монада Багров выполняет все законы.

Список некоторых важных монад в Scala

  • Список
  • Вариант
  • Будущее
  • Пытаться

Надеюсь, вам понравилась история. Хлопайте, если вам это понравилось. Не забывай следовать за мной.