Контейнер

В общем смысле, контейнер или контекст - это абстракция, которая оборачивает или содержит некоторые данные, предоставляя интерфейс для работы с ними.

Пусть у нас есть некоторое значение, допустим, число “10”. Мы можем спокойно применить к нему функцию, допустим “(+)2)”:

> (+) 2 10
12

Теперь возьмём и запакуем наше число “10” в контекст. Используем контекст из типа данных Option/Maybe:

type 'a option = None | Some of 'a

> (+) 2 (Some 10)

Теперь мы не можем просто так применить функцию (+)2 к запакованному значению. Для этого наш контейнер должен предоставлять какие-то функции, с помощью которых мы можем работать с запакованными значениями. Причем результат применения одной и той же функции к одному и тому же значению может быть разным - это зависит от контекста. Это основная идея, на которой базируются функторы, аппликативные функторы, монады.

be01822de6f660845c952b2b4fa7edb6.png

Функторы

<aside> 💡 Функтор - тип данных, который можно рассматривать как контейнер или обертку для других типов данных, и который имеет функцию fmap (сигнатура ниже), позволяющий применять функцию к значениям внутри функтора, не изменяя его структуры.

</aside>

module type Functor = sig
  type 'a t (* t - как раз наша "обертка/контейнер" *)
  val fmap : ('a -> 'b) -> 'a t -> 'b t
  val (<$>): ('a -> 'b) -> 'a t -> 'b t (* тот же самый fmap, просто син.сахар *)
end

Рассмотрим монаду Option - она также является функтором (спойлер - любая монада является функтором).

module OptionFunctor : Functor = struct
  type 'a t = 'a option
  let fmap f = function
    | None -> None
    | Some x -> Some (f x)
end

Допустим у нас есть запакованное в контекст значение “4”: Some 4.

Мы хотим прибавить к значению в контексте число “2”, но просто сделать так мы не сможем:

> Some 4 + 2

Error: This expression has type 'a option
       but an expression was expected of type int

Для этого как раз нам и нужна функция fmap, которое принимает функцию (которая принимает обычное значение и возвращает обычное значение) и запакованное в контекст значение и возвращает запакованное в контекст новое значение:

> fmap (+2) (Some 4)
Some 6

Однако этого всего недостаточно, чтобы предоставленный выше тип являлся функтором. Для того, чтобы он был функтором, необходимо, чтобы для него выполнялось два закона:

<aside> 💡 Закон идентичности (Identity Law): Применение функции fmap с тождественной функцией id к функтору не должно изменить его.

</aside>