Functor 2.9

Functors

Functor 2.9

Consider the function below.

function plus1(value) { return value + 1 }

It is just a function that takes an integer and adds one to it. Similarly we could could have another function plus2. We will use these functions later.

function plus2(value) { return value + 2 }

And we could write a generalised function to use any of these functions as and when required.

function F(value, fn) { return fn(value) } F(1, plus1) ==>> 2

This function will work fine as long as the value passed is an integer. Try an array.

F([1, 2, 3], plus1) ==>> '1,2,31'

Ouch. We took an array of integers, added an integer and got back a string! Not only did it do the wrong thing, we ended up with a string having started with an array. In other words our program also trashed the structure of the input. We want F to do the “right thing”. The right thing is to “maintain structure” through out the operation.

So what do we mean by “maintain structure”? Our function must “unwrap” the given array and get its elements. Then call the given function with every element. Then wrap the returned values in a new Array and return it. Fortunately JavaScript just has that function. Its called map.

[1, 2, 3].map(plus1) ==>> [2, 3, 4]

And map is a functor!

A functor is a function, given a value and a function, does the right thing.

To be more specific.
A functor is a function, given a value and a function, unwraps the values to get to its inner value(s), calls the given function with the inner value(s), wraps the returned values in a new structure, and returns the new structure.

Thing to note here is that depending on the “Type” of the value, the unwrapping may lead to a value or a set of values.

Also the returned structure need not be of the same type as the original value. In the case of map both the value and the returned value have the same structure (Array).

The returned structure can be any type as long as you can get to the individual elements.

So if you had a function that takes and Array and returns value of type Object with all the array indexes as keys, and corresponding values, that will also be a functor.

In the case of JavaScript, filter is a functor because it returns an Array, however forEach is not a functor because it returns undefined. ie. forEach does not maintain structure.

Functors come from category theory in mathematics, where functors are defined as “homomorphisms between categories”. Let's draw some meaning those words.

  • homo = same
  • morphisms = functions that maintain structure
  • category = type

According to the theory, function F is a functor when for two composable ordinary functions f and g

F(f . g) = F(f) . F(g)

where . indicates composition. ie. functors must preserve composition.

So given this equation we can prove wether a given function is indeed a functor or not.

Array Functor

We saw that map is a functor that acts on type Array. Let us prove that the JavaScript Array.map function is a functor.

function compose(f, g) { return function(x) {return f(g(x))}}

Composing functions is about calling a set of functions, by calling the next function, with results of the previous function. Note that our compose function above works from right to left. g is called first then f.

[1, 2, 3].map(compose(plus1, plus2)) ==>> [ 4, 5, 6 ] [1, 2, 3].map(plus2).map(plus1) ==>> [ 4, 5, 6 ]

Yes! map is indeed a functor.

Lets try some functors. You can write functors for values of any type, as long as you can unwrap the value and return a structure.

String Functor

So can we write a functor for type string? Can you unwrap a string? Actually you can, if you think of a string as an array of chars. So it is really about how you look at the value. We also know that chars have char codes which are integers. So we run plus1 on every char charcode, wrap them back to a string and return it.

function stringFunctor(value, fn) { var chars = value.split(“”) return chars.map(function(char) { return String.fromCharCode(fn(char.charCodeAt(0))) }).join(“”) } stringFunctor(“ABCD”, plus1) ==>> “BCDE”

You can begin to see how awesome functors are. You can actually write a parser using the string functor as the basis.

Function Functor

In JavaScript functions are first class citizens. That means you can treat functions any other value. So can we write a functor for value of type function? We should be able to! But how do we unwrap a function? You can unwrap a function by calling it and getting its return value. But we straight away run into a problem.

To call the function we need its arguments. Remember that the functor only has the function that came in as the value. We can solve this by having the functor return a new function.

This function is called with the arguments, and we will in turn call the value function with the argument, and call the original functors function with the value returned!

function functionFunctor(value, fn) { return function(initial) { return function() { return fn(value(initial)) } } } var init = functionFunctor(function(x) {return x * x}, plus1) var final = init(2) final() ==> 5 Our function functor really does nothing much, to say the least. But there a couple things of note here. Nothing happens until you call final.

Every thing is in a state of suspended animation until you call final. The function functor forms the basis for more awesome functional stuff maintaining state, continuation calling and even promises.

You can write your own function functors to do these things!

MayBe Functor

function mayBe(value, fn) { return value === null || value === undefined ? value : fn(value)}

Yes, this is a valid functor.

mayBe(undefined, compose(plus1, plus2)) ==>> undefinedmayBe(mayBe(undefined, plus2), plus1) ==>> undefinedmayBe(1, compose(plus1, plus2)) ==>> 4mayBe(mayBe(1, plus2), plus1) ==>> 4

So mayBe passes our functor test. There is no need for unrapping or wrapping here. It just returns nothing for nothing. Maybe is useful as a short circuiting function, which you can use as a substitute for code

if (result === null) { return null} else { doSomething(result)}

Identity Function

function id(x) { return x}

The function above is known as the identity function. It is just a function that returns the value passed to it. It is called so, because it is the identity in composition of functions in mathematics.

We learned earlier that functors must preserve composition. However something I did not mention then, is that functors must also preserve identity. ie.

F(value, id) = value

Lets try this for map.

[1, 2, 3].map(id) ==>> [ 1, 2, 3 ]

Type Signature

The type signature of a function is the type of its argument and return value. So the type signature of our plus1 function is

f: int -> int

The type signature of the functor map depends on the type signature of the function argument. So if map is called with plus1 then its type signature is

map: [int] -> [int]

However the type signature of the given function need not be the same as above. We could have a function

f: int -> string

in which the type signature of map would be

map: [int] -> [string]

The only restriction being that the type change does not affect the composability of the functor. So in general a functor's type signature can

F: A -> B

In other words map can take an array of integers and return an array of strings and would still be a functor.

Monads are a special case of Functors whos type signature is

M: A -> A

More about monads in the next chapter.

“,”author”:”Santosh Rajan”,”date_published”:”2019-04-18T18:24:00.000Z”,”lead_image_url”:null,”dek”:null,”next_page_url”:null,”url”:”//functionaljavascript.blogspot.com/2013/07/functors.html”,”domain”:”functionaljavascript.blogspot.com”,”excerpt”:”Consider the function below. function plus1(value) { return value + 1 } It is just a function that takes an integer and adds…”,”word_count”:1315,”direction”:”ltr”,”total_pages”:1,”rendered_pages”:1}

Источник: //functionaljavascript.blogspot.com/2013/07/functors.html

Documentation

Functor 2.9

Description

Functors: uniform action over a parameterized type, generalizing the map function on lists.

Synopsis

class Functor f where Source #

The Functor class is used for types that can be mapped over.Instances of Functor should satisfy the following laws:

fmap id == idfmap (f . g) == fmap f . fmap g

The instances of Functor for lists, Maybe and IOsatisfy these laws.

Instances

Functor [] Source #Since: 2.1
Instance detailsDefined in GHC.Base
Functor Maybe Source #Since: 2.1
Instance detailsDefined in GHC.Base
Functor IO Source #Since: 2.1
Instance detailsDefined in GHC.Base
Functor Par1 Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor NonEmpty Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Base
Functor ReadP Source #Since: 2.1
Instance detailsDefined in Text.ParserCombinators.ReadP
Functor ReadPrec Source #Since: 2.1
Instance detailsDefined in Text.ParserCombinators.ReadPrec
Functor Down Source #Since: 4.11.0.0
Instance detailsDefined in Data.Ord
Functor Product Source #Since: 4.8.0.0
Instance detailsDefined in Data.Semigroup.Internal
Functor Sum Source #Since: 4.8.0.0
Instance detailsDefined in Data.Semigroup.Internal
Functor Dual Source #Since: 4.8.0.0
Instance detailsDefined in Data.Semigroup.Internal
Functor Last Source #Since: 4.8.0.0
Instance detailsDefined in Data.Monoid
Functor First Source #Since: 4.8.0.0
Instance detailsDefined in Data.Monoid
Functor STM Source #Since: 4.3.0.0
Instance detailsDefined in GHC.Conc.Sync
Functor Handler Source #Since: 4.6.0.0
Instance detailsDefined in Control.Exception
Functor Identity Source #Since: 4.8.0.0
Instance detailsDefined in Data.Functor.Identity
Functor ZipList Source #Since: 2.1
Instance detailsDefined in Control.Applicative
Functor ArgDescr Source #Since: 4.6.0.0
Instance detailsDefined in System.Console.GetOpt
Functor OptDescr Source #Since: 4.6.0.0
Instance detailsDefined in System.Console.GetOpt
Functor ArgOrder Source #Since: 4.6.0.0
Instance detailsDefined in System.Console.GetOpt
Functor Option Source #Since: 4.9.0.0
Instance detailsDefined in Data.Semigroup
Functor Last Source #Since: 4.9.0.0
Instance detailsDefined in Data.Semigroup
Functor First Source #Since: 4.9.0.0
Instance detailsDefined in Data.Semigroup
Functor Max Source #Since: 4.9.0.0
Instance detailsDefined in Data.Semigroup
Functor Min Source #Since: 4.9.0.0
Instance detailsDefined in Data.Semigroup
Functor Complex Source #Since: 4.9.0.0
Instance detailsDefined in Data.Complex
Functor (Either a) Source #Since: 3.0
Instance detailsDefined in Data.Either
Functor (V1 :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor (U1 :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor ((,) a) Source #Since: 2.1
Instance detailsDefined in GHC.Base
Functor (ST s) Source #Since: 2.1
Instance detailsDefined in GHC.ST
Functor (Proxy :: Type -> Type) Source #Since: 4.7.0.0
Instance detailsDefined in Data.Proxy
Arrow a => Functor (ArrowMonad a) Source #Since: 4.6.0.0
Instance detailsDefined in Control.Arrow
Monad m => Functor (WrappedMonad m) Source #Since: 2.1
Instance detailsDefined in Control.Applicative
Functor (ST s) Source #Since: 2.1
Instance detailsDefined in Control.Monad.ST.Lazy.Imp
Functor (Arg a) Source #Since: 4.9.0.0
Instance detailsDefined in Data.Semigroup
Functor f => Functor (Rec1 f) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor (URec Char :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor (URec Double :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor (URec Float :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor (URec Int :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor (URec Word :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor (URec (Ptr ()) :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
Functor f => Functor (Alt f) Source #Since: 4.8.0.0
Instance detailsDefined in Data.Semigroup.Internal
Functor f => Functor (Ap f) Source #Since: 4.12.0.0
Instance detailsDefined in Data.Monoid
Functor (Const m :: Type -> Type) Source #Since: 2.1
Instance detailsDefined in Data.Functor.Const
Arrow a => Functor (WrappedArrow a b) Source #Since: 2.1
Instance detailsDefined in Control.Applicative
Functor ((->) r :: Type -> Type) Source #Since: 2.1
Instance detailsDefined in GHC.Base
Functor (K1 i c :: Type -> Type) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
(Functor f, Functor g) => Functor (f :+: g) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
(Functor f, Functor g) => Functor (f :*: g) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
(Functor f, Functor g) => Functor (Sum f g) Source #Since: 4.9.0.0
Instance detailsDefined in Data.Functor.Sum
(Functor f, Functor g) => Functor (Product f g) Source #Since: 4.9.0.0
Instance detailsDefined in Data.Functor.Product
Functor f => Functor (M1 i c f) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
(Functor f, Functor g) => Functor (f :.: g) Source #Since: 4.9.0.0
Instance detailsDefined in GHC.Generics
(Functor f, Functor g) => Functor (Compose f g) Source #Since: 4.9.0.0
Instance detailsDefined in Data.Functor.Compose

( a -> f b -> f a infixl 4 Source #

Replace all locations in the input with the same value. The default definition is fmap . const, but this may be overridden with a more efficient version.

($>) :: Functor f => f a -> b -> f b infixl 4 Source #

Flipped version of >>Nothing $> “foo”Nothing>>>Just 90210 $> “foo”Just “foo”

Replace the contents of an Either Int Int with a constant String, resulting in an Either Int String:

>>>Left 8675309 $> “foo”Left 8675309>>>Right 8675309 $> “foo”Right “foo”

Replace each element of a list with a constant String:

>>>[1,2,3] $> “foo”[“foo”,”foo”,”foo”]

Replace the second element of a pair with a constant String:

>>>(1,2) $> “foo”(1,”foo”)

Since: 4.7.0.0

() :: Functor f => (a -> b) -> f a -> f b infixl 4 Source #

An infix synonym for fmap.

The name of this operator is an allusion to $. Note the similarities between their types:

($) :: (a -> b) -> a -> b() :: Functor f => (a -> b) -> f a -> f b

Whereas $ is function application, is function application lifted over a Functor.

Examples

Expand

Convert from a Maybe Int to a Maybe String using show:

>>>show NothingNothing>>>show Just 3Just “3”

Convert from an Either Int Int to an Either Int String using show:

>>>show Left 17Left 17>>>show Right 17Right “17”

Double each element of a list:

>>>(*2) [1,2,3][2,4,6]

Apply even to the second element of a pair:

>>>even (2,2)(2,True)

() :: Functor f => f a -> (a -> b) -> f b infixl 1 Source #

Flipped version of .

() = flip fmap

Функтор в JavaScript

Functor 2.9

Рассмотрим следующий код:

function plus1(value) { return value + 1 }

Это простая функция, которая получает числовое значение и добавляет к нему единицу. Аналогичным образом мы можем сделать функцию plus2. Мы будем позже использовать эти функции:

function plus2(value) { return value + 2 }

Или мы можем написать обобщенную функцию, чтоб использовать любую из этих функций по необходимости:

function F(value, fn) { return fn(value) }F(1, plus1) ==>> 2

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

F([1, 2, 3], plus1) ==>> '1,2,31'

Упс! Мы взяли коллекцию целых чисел, добавили целое число и получили строку! Мало того, что оно сделало все неверно, так еще и вернуло строку как результат от коллекции. Другими словами, наша программа отправила входные данные в корзину. Нам нужно, чтобы F делала “правильные вещи”. То есть, правильно обрабатывала структуру на протяжении всей операции.

Что значит “обрабатывала структуру”? Наша функция должна “распаковать” данную коллекцию и получить ее элементы. Потом вызвать полученную функцию с каждым элементом. После чего “запаковать” полученое значение в новую коллекцию, которую собственно и вернуть. К счастью, JavaScript уже имеет такую. Она называется map:

[1, 2, 3].map(plus1) ==>> [2, 3, 4]

И map это функтор!

Функтор – это функция, которая получает значения и функцию, и делает “правильные вещи”.

Если поточнее:

Функтор – функция, которая получает значения и функцию, распаковывает значения для получения их внутренние значение(значения), вызывает полученную функцию с внутренними значениям, запаковывает возвращенные значения в новую структуру, и впоследствии возвращает новую структуру.

Необходимо отметить, что это зависит от “типа” значения, распаковка может приводить к значению или набора значений.

Так же возвращенная структура не обязательно должна иметь тот же тип, что и оригинальная. В случает с map оба значение и возвращенное значение имеют одинаковую структуру (Коллекцию).

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

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

В случае с JavaScript, filter так же является функтором, так как возвращает коллекцию, хотя forEach им не является, так как возвращает undefined, т. е. не сохраняет структуру.

Функторы пришли из теории категорий в математике, где функторы определены как “гомоморфизм между категориями”. Немного разберемся, что значат этот набор слов:

homo = одна и та же

morphisms = функции которые сохраняют структуру

category = тип

Согласно теории, функция F является функтором, когда две компонуемы обычные функции f и g:

F(f . g) = F(f) . F(g)

где . указывает на композицию, т. е. функторы должны поддерживать композицию.

Поэтому, это уравнение может быть использовано для доказательства того, является функция функтором или нет.

Фукнтор коллекций

Мы видели, что map является функтором, который работает с типом Array. Давайте докажем, что функция Array.map – это функтор:

function compose(f, g) { return function(x) {return f(g(x))}}

Композиция функций – это вызов набора функций, вызов вызова следующей функции, с результатами от предыдущей. Обратите внимание, что композиция функций работает слева направо. Первой будет вызвана g, потом f:

[1, 2, 3].map(compose(plus1, plus2)) ==>> [ 4, 5, 6 ] [1, 2, 3].map(plus2).map(plus1) ==>> [ 4, 5, 6 ]

Да! map однозначно функтор.

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

Функтор строк

Итак, можем ли мы написать фукнтор строк? Можно ли распаковать строку? В действительности можно, если рассматривать строку как коллекцию символов. Т. е. на деле все зависит от того, под каким углом смотреть на значение. Мы так же знаем, что символы имеют коды, которые на деле являются числом. Поэтому, вы выполним plus1 на каждом коде символа, и запакуем обратно в строку:

function stringFunctor(value, fn) { var chars = value.split(“”) return chars.map(function(char) { return String.fromCharCode(fn(char.charCodeAt(0))) }).join(“”) } stringFunctor(“ABCD”, plus1) ==>> “BCDE”

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

Функтор функции

В JavaScript функции полноправные жители (first class citizens). Это значит, что вы можете рассматривать функции как любое другое значение. Итак, можем ли мы написать функтор для типа, где значение является функцией? Мы должны! Но как же распаковать функцию? Можем распаковать ее, просто вызвав, и получить ее значение. Но мы тут же столкнемся с проблемой.

Для вызова функции, нам необходимы ее аргументы. Запомните, что функтор – это функция, которая приходит к нам как значение. Мы можем решить эту дилемму, заставив функтор вернуть новую функцию.

Эта функция вызывается с аргументами, а мы, в свою очередь, вызовем функцию значения с аргументом, после вызываем оригинальную функцию-функтор с возвращенным значением!

function functionFunctor(value, fn) { return function(initial) { return function() { return fn(value(initial)) } } } var init = functionFunctor(function(x) {return x * x}, plus1) var final = init(2) final() ==> 5Наш функтор функции, на самом деле, не очень-то много и делает. Но есть несколько моментов, которые необходимо отметить. Ничего не произойдет, пока вы не вызовете final.

Все находится в состоянии анабиоза, пока вы не вызовете final. Функтор функции – это база для других функциональных штук, например, управление состоянием, непрерывный вызов и даже обещания.

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

Функтор MayBe

function mayBe(value, fn) { return value === null || value === undefined ? value : fn(value)}

Да, это правильный функтор.

mayBe(undefined, compose(plus1, plus2)) ==>> undefinedmayBe(mayBe(undefined, plus2), plus1) ==>> undefinedmayBe(1, compose(plus1, plus2)) ==>> 4mayBe(mayBe(1, plus2), plus1) ==>> 4

Итак mayBe – это функтор тестирования. Здесь нет необходимости распаковывания или запаковывания. Он просто ничего не возвращает, если ничего не передать. mayBe полезен для короткой проверки на неопределенность значения; можно его использовать как замену для следующего кода:

if (result === null) { return null} else { return doSomething(result)}

Функция Идентичности

function id(x) { return x}

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

F(value, id) = value

Давайте проверим это на map.

[1, 2, 3].map(id) ==>> [ 1, 2, 3 ]

Сигнатура типа

Сигнатура функции – это тип ее аргументов и возвращаемого значения. Итак, сигнатура функции plus:

f: int -> int

Сигнатура функтора map зависит от сигнатуры аргумент-функции. Если map была вызвана с plus1, тогда ее сигнатура будет:

map: [int] -> [int]

Хотя сигнатура этой функции не обязательно должна быть как указано выше. У нас могла быть следующая функция:

f: int -> string

в этом случае сигнатура map может быть:

map: [int] -> [string]

Единственное ограничение – это то, что смена типа не влияет на компонуемость функтора. В общем, сигнатура функтора может быть:

F: A -> B

Другими словами, map может получить коллекцию целых чисел и вернуть коллекцию строк и при том все же быть функтором.

Монады – это специальный тип функторов, сигнатура которых:

M: A -> A

Подробнее о монадах в следующей главе.

Источник

Источник: //sqrtt.pro/functors-ru

Boost.Hana: Functor

Functor 2.9

The Functor concept represents types that can be mapped over.

Intuitively, a Functor is some kind of box that can hold generic data and map a function over this data to create a new, transformed box.

Because we are only interested in mapping a function over the contents of a black box, the only real requirement for being a functor is to provide a function which can do the mapping, along with a couple of guarantees that the mapping is well-behaved.

Those requirements are made precise in the laws below. The pattern captured by Functor is very general, which makes it widely useful.

A lot of objects can be made Functors in one way or another, the most obvious example being sequences with the usual mapping of the function on each element. While this documentation will not go into much more details about the nature of functors, the Typeclassopedia is a nice Haskell-oriented resource for such information.

Functors are parametric data types which are parameterized over the data type of the objects they contain. everywhere else in Hana, this parametricity is only at the documentation level and it is not enforced.

In this library, the mapping function is called transform after the std::transform algorithm, but other programming languages have given it different names (usually map).

NoteThe word functor comes from functional programming, where the concept has been used for a while, notably in the Haskell programming language. Haskell people borrowed the term from category theory, which, broadly speaking, is a field of mathematics dealing with abstract structures and transformations between those structures.

Minimal complete definitions

  1. transform
    When transform is specified, adjust_if is defined analogously to adjust_if(xs, pred, f) = transform(xs, [](x){ if pred(x) then f(x) else x

    })

  2. adjust_if
    When adjust_if is specified, transform is defined analogously to transform(xs, f) = adjust_if(xs, always(true), f)

Laws

Let xs be a Functor with tag F(A), \( f : A \to B \) and \( g : B \to C \). The following laws must be satisfied:

The first line says that mapping the identity function should not do anything, which precludes the functor from doing something nasty behind the scenes. The second line states that mapping the composition of two functions is the same as mapping the first function, and then the second on the result.

While the usual functor laws are usually restricted to the above, this library includes other convenience methods and they should satisfy the following equations.

Let xs be a Functor with tag F(A), \( f : A \to A \), \( \mathrm{pred} : A \to \mathrm{Bool} \) for some Logical Bool, and oldval, newval, value objects of tag A. Then,
adjust(xs, value, f) == adjust_if(xs, equal.to(value), f)adjust_if(xs, pred, f) == transform(xs, [](x){ if pred(x) then f(x) else x

})

replace_if(xs, pred, value) == adjust_if(xs, pred, always(value))replace(xs, oldval, newval) == replace_if(xs, equal.to(oldval), newval)fill(xs, value) == replace_if(xs, always(true), value)

The default definition of the methods will satisfy these equations.

Structure-preserving functions for Functors

A mapping between two functors which also preserves the functor laws is called a natural transformation (the term comes from category theory). A natural transformation is a function f from a functor F to a functor G such that for every other function g with an appropriate signature and for every object xs of tag F(X),

f(transform(xs, g)) == transform(f(xs), g)

There are several examples of such transformations, to when applied to an optional value. Indeed, for any function g and hana::optional opt,

to(transform(opt, g)) == transform(to(opt), g)

Of course, natural transformations are not limited to the to functions. However, note that any conversion function between Functors should be natural for the behavior of the conversion to be intuitive.

constexpr auto boost::hana::adjust
Apply a function on all the elements of a structure that compare equal to some value. More…
constexpr auto boost::hana::adjust_if
Apply a function on all the elements of a structure satisfying a predicate.Given a Functor, a predicate pred and a function f, adjust_if will adjust the elements of the Functor that satisfy the predicate with the function f. In other words, adjust_if will return a new Functor equal to the original one, except that the elements satisfying the predicate will be transformed with the given function. Elements for which the predicate is not satisfied are left untouched, and they are kept as-is in the resulting Functor. More…
constexpr auto boost::hana::fill
Replace all the elements of a structure with a fixed value. More…
constexpr auto boost::hana::replace
Replace all the elements of a structure that compare equal to some value with some new fixed value. More…
constexpr auto boost::hana::replace_if
Replace all the elements of a structure satisfying a predicate with a fixed value. More…
constexpr auto boost::hana::transform
Map a function over a Functor. More…
constexpr auto boost::hana::adjust

#include

Initial value:= [](auto&& xs, auto&& value, auto&& f) {

return tag-dispatched;

}

constexpr auto value

Return the compile-time value associated to a constant.This function returns the value associated to …

Definition: value.hpp:54

Apply a function on all the elements of a structure that compare equal to some value.

Signature

Given F a Functor and U a type that can be compared with T's, the signature is \( \mathtt{adjust} : F(T) \times U \times (T \to T) \to F(T) \)

Parameters

xsThe structure to adjust with f.
valueAn object that is compared with each element x of the structure. Elements of the structure that compare equal to value are adjusted with the f function.
fA function called as f(x) on the element(s) of the structure that compare equal to value.

Example

#include

#include

#include

#include

#include

namespace hana = boost::hana;BOOST_HANA_CONSTEXPR_LAMBDA auto negate = [](auto x) {

return -x;

};

int main() {

BOOST_HANA_CONSTEXPR_CHECK( hana::adjust(hana::make_tuple(1, 4, 9, 2, 3, 4), 4, negate)

==

hana::make_tuple(1, -4, 9, 2, 3, -4)

);

}

constexpr auto boost::hana::adjust_if

#include

Initial value:

= [](auto&& xs, auto const& pred, auto const& f) {

return tag-dispatched;

}

Apply a function on all the elements of a structure satisfying a predicate.Given a Functor, a predicate pred and a function f, adjust_if will adjust the elements of the Functor that satisfy the predicate with the function f.

In other words, adjust_if will return a new Functor equal to the original one, except that the elements satisfying the predicate will be transformed with the given function.

Elements for which the predicate is not satisfied are left untouched, and they are kept as-is in the resulting Functor.

Записки программиста

Functor 2.9

Мы с вами уже перестали бояться монад, но всякие непонятные стрелочки в коде типа , , и по-прежнему повергают нас в ужас. Сегодня мы выясним, что и здесь бояться особо нечего. Как и в случае с монадами, здесь имеют место быть обыкновенные классы типов и функции для работы с ними. Все просто.

Функторы

Функтор — это просто такой класс для типов, к которым можно применить отображение (map):

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Для fmap должны выполняться следующие законы:

fmap id = id
fmap (p . q) = (fmap p) . (fmap q)

Следует отметить, что в GHC есть расширение DeriveFunctor, позволяющее писать deriving Functor.

Типичные функторы — это Maybe, Either и, конечно же, списки. Для списков fmap определен просто как map. Обратите внимание, что он не может быть определен как reverse . map g, потому что это нарушило бы первый закон. Кроме того, часто экземпляр класса типов Functor объявляется для различных контейнеров. Например, Data.Map также является функтором.

У функции fmap есть инфиксная форма:

() :: Functor f => (a -> b) -> f a -> f b

Из любой монады можно получить функтор:

ghci> let fmap' f ma = ma >>= (return . f)ghci> :t fmap'

fmap' :: Monad m => (a -> b) -> m a -> m b

Или в do-нотации:

fmap' f ma = do
  a x x

“fedcba”

Тем не менее, в общем случае без явного объявления экземпляра класса типов никакого функтора из монады вы не получите.

Пока несложно, правда?

Аппликативные функторы

Функтор — это когда данные, обернутые в некоторый контейнер или контекст, мы можем отобразить (map) путем применения некой функции. Аппликативные функторы — это такой шаг вперед по сравнению с функторами. Здесь в контекст оборачиваются не только данные, но и функции:

— Control.Applicative

class Functor f => Applicative f where

  pure :: a -> f a
  () :: f (a -> b) -> f a -> f b

  (*>) :: f a -> f b -> f b

  u *> v = pure (const id) u v

  ( f b -> f a

  u Just (+1) Just 3Just 4ghci> Just (+1) NothingNothingghci> Nothing Just 3

Nothing

А вот пример со списками:

ghci> [ (+1), (*2) ] [1,2,3][2,3,4,2,4,6]

То есть, функция pure как бы оборачивает функцию в контекст, а применяет функцию в контексте к данным в контексте. Использование операторов *> и :t const
const :: a -> b -> a

Есть такой забавный фокус:

ghci> :t const id
const id :: b -> a -> a

Если вам это сломало мозг, тут объясняется, как это работает. Это несложно.

По умолчанию определены так:

u v = pure (const id) u v

Например:

ghci> Just 1 [1,2,3] Just 1 *> Just 2Just 2ghci> [1,2,3] *> [4,5,6] [4,5,6,4,5,6,4,5,6]

Куда указывает стрелочка, то значение и остается. Ну и в соответствии с семантикой , если один из аргументов является Nothing или пустым списком, то и в итоге получаем Nothing или пустой список.

Аппликативные функторы должны подчиняться следующим законам:

— | Identity
pure id v = v
— | Composition
pure (.) u v w = u (v w)
— | Homomorphism
pure f pure x = pure (f x)
— | Interchange
u pure y = pure ($ y) u

На практике, конечно же, их редко кто доказывает строго, обычно достаточно написать соответствующие тесты.

Синергия классов типов Functor и Applicative

Как вы, конечно же, заметили, Applicative является подклассом Functor. Это означает, что стрелочки и часто используются совместно. Допустим, у нас есть такая функция:

ghci> let f x = if x == 0 then Nothing else Just (1 / x)ghci> f 0Nothing ghci> f 3

Just 0.3333333333333333

Теперь мы хотим применить ее к трем числам — x, y и z. Если хотя бы для одного числа функция возвращает Nothing, то и весь результат должен быть Nothing. Иначе мы должны вернуть Just (1 / x, 1 / y, 1 / z). Как мы помним, Maybe является монадой, что позволяет вместо всяких вложенных if, then, else написать:

g x y z = do
  x' c -> (a, b, c)ghci> (,,) 1 2 3

(1,2,3)

Держим в уме, что запись a -> b -> c -> (a, b, c) равносильна a -> (b -> c -> (a, b, c)).

ghci> :t f 1f 1 :: Maybe Doubleghci> :t ()() :: Functor f => (a -> b) -> f a -> f bghci> :t (,,) f 1

(,,) f 1 :: Maybe (b -> c -> (Double, b, c))

Смотрите, у нас были некие данные (результат применения функции f к 1) в контексте Maybe и мы успешно применили (частично, благодаря каррированию) функцию (,,) к этому контексту, поскольку Maybe является функтором.

В итоге мы получили новую функцию от двух аргументов в контексте Maybe. Теперь нам нужно применить полученную функцию в контексте к следующей порции данных в контексте.

Но постойте-ка, ведь для этого и нужны аппликативные функторы!

ghci> :t (,,) f 1 f 2(,,) f 1 f 2 :: Maybe (c -> (Double, Double, c))ghci> :t (,,) f 1 f 2 f 3(,,) f 1 f 2 f 3 :: Maybe (Double, Double, Double)ghci> (,,) f 1 f 2 f 3

Just (1.0,0.5,0.3333333333333333)

Вместо четырех строчек кода в do-нотации мы обошлись всего лишь одной. Разве это не здорово?

Также имеются функции liftA2, liftA3 и так далее, делающие то же самое:

ghci> liftA2 (,) (f 1) (f 2)Just (1.0, 0.5)ghci> :t liftA2

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

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

Дополнение: Тихо и незаметно Applicative стал суперклассом Monad.

Моноиды

Моноиды в Haskell используются вообще везде.

— Data.Monoid

class Monoid a where

  mempty :: a
  mappend :: a -> a -> a

  mconcat :: [a] -> a

  mconcat = foldr mappend mempty

Моноид — это просто такая штука, которую можно «складывать» (mappend) с другими такими же штуками и у которой имеется нейтральный элемент (mempty), который при сложении с любым другим элементом дает этот же элемент.

У mappend есть более короткая запись:

() :: Monoid m => m -> m -> m

Законы для моноидов:

mempty x = xx mempty = xx (y z) = (x y) z

mconcat = foldr () mempty

Список является типичным моноидом. Для него mempty — это пустой список, а mappend — операция ++:

ghci> “aaa” “bbb””aaabbb”ghci> mempty “abc”

“abc”

Еще моноидами являются Set’ы, Map’ы, Maybe, а также Text, BinaryString и их билдеры. Для многих типов можно объявить более одного экземпляра класса типов Monoid.

Например, для чисел можно выбрать в качестве mempty и mappend как число 0 и операцию сложения, так и число 1 и операцию умножения.

Эта проблема в Haskell решается путем объявления newtype’ов, в случае с числами это соответственно Sum и Product.

Заключение

Дополнительные материалы:

Помимо названных, в Haskell есть еще много занятных классов типов.

Например, Foldable, для всего, что может сворачиваться (fold), Traversable для всего, что можно обойти слева направо, Alternative, представляющий собой моноид над аппликативными функторами (та самая непонятная стрелочка ) и другие.

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

Haskell, Функциональное программирование.

Источник: //eax.me/functor-applicative-and-monoid/

Поделиться:
Нет комментариев

    Добавить комментарий

    Ваш e-mail не будет опубликован. Все поля обязательны для заполнения.

    ×
    Рекомендуем посмотреть