Portability | portable |
---|---|

Stability | experimental |

Maintainer | peter.trsko@gmail.com |

Safe Haskell | None |

During development it is common occurrence to modify deeply nested structures. One of the best known libraries for this purpose is lens, but it's quite overkill for some purposes.

This module describes simple and composable combinators that are built on top of very basic concept:

f`.`

h`.`

g

Where `f`

and `g`

are fixed. It is possible to reduce it to just:

(f`.`

)`.`

(`.`

g)

Which is the core pattern used by all functions defined in this module.

Trying to generalize this pattern furhter ends as:
`(f `

, where
`<$>`

) `.`

(`<$>`

g)

. Other combinations of substituting `<$>`

= `fmap`

`.`

for
`fmap`

will end up less or equally generic. Type of such expression is:

\f g -> (f <$>) . (<$> g) :: Functor f => (b -> c) -> f a -> (a -> b) -> f c

Which doesn't give us much more power. Instead of going for such
generalization we kept the original `((f `

which we named
`.`

) `.`

(`.`

g))`between`

or `~@~`

in its infix form.

- between :: (c -> d) -> (a -> b) -> (b -> c) -> a -> d
- (~@~) :: (c -> d) -> (a -> b) -> (b -> c) -> a -> d
- (~@@~) :: (a -> b) -> (c -> d) -> (b -> c) -> a -> d
- (^@~) :: (a -> c -> d) -> (a -> b) -> (b -> c) -> a -> d
- (~@@^) :: (a -> b) -> (a -> c -> d) -> (b -> c) -> a -> d
- (^@^) :: (a -> d -> e) -> (a -> b -> c) -> (c -> d) -> a -> b -> e
- (^@@^) :: (a -> b -> c) -> (a -> d -> e) -> (c -> d) -> a -> b -> e
- between2l :: (c -> d) -> (a -> b) -> (b -> b -> c) -> a -> a -> d
- between3l :: (c -> d) -> (a -> b) -> (b -> b -> b -> c) -> a -> a -> a -> d
- (<~@~>) :: (Functor f, Functor g) => (c -> d) -> (a -> b) -> (f b -> g c) -> f a -> g d
- (<~@@~>) :: (Functor f, Functor g) => (a -> b) -> (c -> d) -> (f b -> g c) -> f a -> g d
- (<~@~) :: Functor f => (c -> d) -> (a -> b) -> (b -> f c) -> a -> f d
- (~@@~>) :: Functor f => (a -> b) -> (c -> d) -> (b -> f c) -> a -> f d
- (~@~>) :: Functor f => (c -> d) -> (a -> b) -> (f b -> c) -> f a -> d
- (<~@@~) :: Functor f => (a -> b) -> (c -> d) -> (f b -> c) -> f a -> d
- (<^@~) :: Functor f => (a -> c -> d) -> (a -> b) -> (b -> f c) -> a -> f d
- (~@@^>) :: Functor f => (a -> b) -> (a -> c -> d) -> (b -> f c) -> a -> f d
- (<^@^>) :: (Functor f, Functor g) => (a -> d -> e) -> (a -> b -> c) -> (f c -> g d) -> a -> f b -> g e
- (<^@@^>) :: (Functor f, Functor g) => (a -> b -> c) -> (a -> d -> e) -> (f c -> g d) -> a -> f b -> g e
- (<^@^) :: Functor f => (a -> d -> e) -> (a -> b -> c) -> (c -> f d) -> a -> b -> f e
- (^@@^>) :: Functor f => (a -> b -> c) -> (a -> d -> e) -> (c -> f d) -> a -> b -> f e
- (^@^>) :: Functor f => (a -> d -> e) -> (a -> b -> c) -> (f c -> d) -> a -> f b -> e
- (<^@@^) :: Functor f => (a -> b -> c) -> (a -> d -> e) -> (f c -> d) -> a -> f b -> e

# Composability

(f . h) ~@~ (i . g) === (f ~@~ g) . (h ~@~ i)

This shows us that it is possible to define `(f `

and `~@~`

g)`(h `

separately, for reusability, and then compose them.
`~@~`

i)

The fun doesn't end on functions that take just one parameter, because `~@~`

lets you build up things like:

(f ~@~ funOnY) ~@~ funOnX === \g x y -> f (g (funOnX x) (funOnY y))

As you can se above `g`

is a function that takes two parameters. Now we can
define `(f `

separately, then when ever we need we can extend
it to higher arity function by appending `~@~`

funOnY)`(`

. Special case when
`~@~`

funOnX)`funOnY = funOnX`

is very interesting, in example function
`on`

can be defined using `between`

as:

on :: (b -> b -> c) -> (a -> b) -> a -> a -> c on f g = (id ~@~ g ~@~ g) f -- or: ((. g) ~@~ g) f

We can also define function `on3`

that takes function with arity three as
its first argument:

on3 :: (b -> b -> b -> d) -> (a -> b) -> a -> a -> a -> d on3 f g = (id ~@~ g ~@~ g ~@~ g) f -- or: ((. g) ~@~ g ~@~ g) f

If we once again consider generalizing above examples by using three
different functions `g1 =/= g2 =/= g3`

instead of just one `g`

then we
get:

on' :: (b -> b1 -> c) -> (a2 -> b2) -> (a1 -> b1) -> a1 -> a2 -> c on' f g1 g2 = (id ~@~ g2 ~@~ g1) f on3' :: (b1 -> b2 -> b3 -> c) -> (a3 -> b3) -> (a2 -> b2) -> (a1 -> b1) -> a1 -> a2 -> a3 -> c on3' f g1 g2 g3 = (id ~@~ g3 ~@~ g2 ~@~ g1) f

Which allows us to interpret `~@~`

in terms like "apply this function to
the n-th argument before passing it to the function `f`

". We just have to
count the arguments backwards. In example if want to apply function `g`

to
third argument, but no other then we can use:

\g f -> (id ~@~ g ~@~ id ~@~ id) f -- ^ ^ ^ ^- Applied to the first argument. -- | | '- Applied to the second argument. -- | '- Applied to the third argument. -- '- Applied to the result. :: (a3 -> b3) -> (a1 -> a2 -> b3 -> c) -> a1 -> a2 -> a3 -> c

Or we can use `~@@~`

, which is just flipped version of `~@~`

and then it
would be:

\g f -> (id ~@@~ id ~@@~ g ~@@~ id) f -- ^ ^ ^ ^- Applied to the result. -- | | '- Applied to the third argument. -- | '- Applied to the second argument. -- '- Applied to the first argument. :: (a3 -> b3) -> (a1 -> a2 -> b3 -> c) -> a1 -> a2 -> a3 -> c

Another interesting situation is when `f`

and `g`

in `(f `

form an
isomorphism. Then we can construct a mapping function that takes function
operating on one type and transform it in to a function that operates on a
different type. As we shown before it is also possible to map functions with
higher arity then one.
`~@~`

g)

Simplicity of how `between`

combinator can be used to define set of
functions by reusing previous definitions makes it also very suitable for
usage in TemplateHaskell and generic programming.

# Mapping Functions For Newtypes

When we use `(f `

where `~@~`

g)`f`

and `g`

form an isomorphism of two
types, and if `f`

is a constructor and `g`

a selector of newtype, then
`(f `

is a mapping function that allows us to manipulate value
wrapped inside a newtype.
`~@~`

g)

newtype T t a = T {fromT :: a} mapT :: (a -> b) -> T t a -> T t' b mapT = T ~@~ fromT

Note that `mapT`

above is generalized version of `fmap`

of obvious `Functor`

instance for newtype `T`

.

Interestingly, we can use `between`

to define higher order mapping functions
by simple chaining:

mapT2 :: (a -> b -> c) -> T t1 a -> T t2 b -> T t3 c mapT2 = mapT ~@~ fromT -- or: T ~@~ fromT ~@~ fromT -- or: mapT `between2l` fromT mapT3 :: (a -> b -> c -> d) -> T t1 a -> T t2 b -> T t3 c -> T t4 d mapT3 = mapT2 ~@~ fromT -- or: T ~@~ fromT ~@~ fromT ~@~ fromT -- or: mapT `between3l` fromT

Dually to definition of `mapT`

and `mapT2`

we can also define:

comapT :: (T a -> T b) -> a -> b comapT = fromT ~@~ T -- or: T ~@@~ fromT comapT2 :: (T a -> T b -> T c) -> a -> b -> c comapT2 = fromT ~@~ T ~@~ T -- or: comapT ~@~ T -- or: T ~@@~ T ~@@~ fromT -- or: T ~@@~ comapT -- or: fromT `between2l` T

In code above we can read code like:

fromT`~@~`

T`~@~`

T

or

T`~@@~`

T`~@@~`

fromT

as "Apply `T`

to first and second argument before passing it to a function
and apply `fromT`

to its result."

Here is another example with a little more complex type wrapped inside a newtype:

newtype T e a = T {fromT :: Either e a} mapT :: (Either e a -> Either e' b) -> T e a -> T e' b mapT = T ~@~ fromT mapT2 :: (Either e1 a -> Either e2 b -> Either e3 c) -> T e1 a -> T e2 b -> T e3 c mapT2 = mapT ~@~ fromT

This last example is typical for monad transformers:

newtype ErrorT e m a = ErrorT {runErrorT :: m (Either e a)} mapErrorT :: (m (Either e a) -> m' (Either e' b)) -> ErrorT e m a -> ErrorT e' m' b mapErrorT = ErrorT ~@~ runErrorT mapErrorT2 :: (m1 (Either e1 a) -> m2 (Either e2 b) -> m3 (Either e3 c)) -> ErrorT e1 m1 a -> ErrorT e2 m2 b -> ErrorT e3 m3 c mapErrorT2 = mapErrorT ~@~ runErrorT

# Constructing Lenses

Library *lens* is notorious for its huge list of (mostly transitive)
dependencies. However it is easy to define a lot of things without the need
to depend on *lens* directly. This module defines few functions that will
make it even easier.

Lens for a simple newtype:

newtype T a = T {fromT :: a} t :: Functor f => (a -> f b) -> T a -> f (T b) t = fmap T ~@~ fromT

To simplify things we can use function `<~@~`

:

t :: Functor f => (a -> f b) -> T a -> f (T b) t = T <~@~ fromT

Lets define lenses for generic data type, e.g. something like:

data D a b = D {_x :: a, _y :: b}

Their types in *lens* terms would be:

x :: Lens (D a c) (D b c) a b y :: Lens (D c a) (D c b) a b

Here is how implementation can look like:

x :: Functor f => (a -> f b) -> D a c -> f (D b c) x = _x ~@@^> \s b -> s{_x = b}

Alternative definitions:

x = (\s b -> s{_x = b}) <^@~ _x x f s = (_x ~@@~> \b -> s{_x = b}) f s x f s = ((\b -> s{_x = b}) <~@~ _x) f s x f s = (const _x ^@@^> \s' b -> s'{_x = b}) f s s x f s = ((\s' b -> s'{_x = b}) <^@^ const _x) f s s

And now for `y`

we do mostly the same:

y :: Functor f => (a -> f b) -> D c a -> f (D c b) y = _y ~@@^> \s b -> s{_y = b}

Above example shows us that we are able to define function equivalent to
`lens`

from *lens* package as follows:

lens :: (s -> a) -- ^ Selector function. -> (s -> b -> t) -- ^ Setter function. -> (forall f. Functor f => (a -> f b) -> s -> f t) -- ^ In /lens/ terms this is @Lens s t a b@ lens = (~@@^>)

Alternative definitions:

lens get set f s = (const get ^@@^> set) f s s lens get set f s = (set <^@^ const get) f s s lens get set f s = (get ~@~> set s) f s lens get set f s = (set s <~@~ get) f s

Some other functions from
lens package can be defined using
`~@~`

:

set :: ((a -> Identity b) -> s -> Identity t) -> b -> s -> t set = (runIdentity .)`~@~`

(`const`

. Identity)

```
over :: ((a -> Identity b) -> s -> Identity t) -> (a -> b) -> s -> t
over = (runIdentity .)
````~@~`

(Identity .)

Data type `Identity`

is defined in
transformers package.

# Between Function Combinator

# Derived Combinators

(^@~) :: (a -> c -> d) -> (a -> b) -> (b -> c) -> a -> dSource

As `~@~`

, but first function is also parametrized with `a`

, hence the name
`^@~`

. Character `^`

indicates which argument is parametrized with
additional argument.

This function is defined as:

(f`^@~`

g) h a -> (f a`~@~`

g) h a

Fixity is left associative and set to value 8, which is one less then
fixity of function composition (`.`

).

(^@^) :: (a -> d -> e) -> (a -> b -> c) -> (c -> d) -> a -> b -> eSource

Pass additional argument to first two function arguments.

This function is defined as:

(f`^@^`

g) h a b -> (f a`~@~`

g a) h b

See also `^@~`

to note the difference, most importantly that `^@~`

passes
the same argument to all its functional arguments. Function `^@~`

can be
defined in terms of this one as:

(f`^@~`

g) h a = (f`^@^`

`const`

g) h a a

We can do it also the other way around and define `^@^`

using `^@~`

:

f`^@^`

g =`curry`

. (f .`snd`

`^@~`

`uncurry`

g)

Fixity is set to value 8, which is one less then of function composition
(`.`

).

between2l :: (c -> d) -> (a -> b) -> (b -> b -> c) -> a -> a -> dSource

Apply function `g`

to each argument of binary function and `f`

to its
result. In suffix "2l" the number is equal to arity of the function it
accepts as a third argument and character "l" is for "left associative".

`between2l`

f g = (f`~@~`

g)`~@~`

g

Interesting observation:

(\f g ->`between2l`

`id`

g f) ===`on`

between3l :: (c -> d) -> (a -> b) -> (b -> b -> b -> c) -> a -> a -> a -> dSource

Apply function `g`

to each argument of ternary function and `f`

to its
result. In suffix "3l" the number is equal to arity of the function it
accepts as a third argument and character "l" is for "left associative".

This function is defined as:

`between3l`

f g = ((f`~@~`

g)`~@~`

g)`~@~`

g

Alternatively it can be defined using `between2l`

:

`between3l`

f g =`between2l`

f g`~@~`

g

## Lifted Combinators

(<~@~) :: Functor f => (c -> d) -> (a -> b) -> (b -> f c) -> a -> f dSource

Apply fmap to first argument of `~@~`

. Dual to `~@~>`

which applies
`fmap`

to second argument.

Defined as:

f`<~@~`

g =`fmap`

f`~@~`

g

This function allows us to define lenses mostly for pair of functions that form an isomorphism. See section Constructing Lenses for details.

Name of `<~@~`

simply says that we apply `<$>`

(`fmap`

) to
first (left) argument and then we apply `~@~`

.

Fixity is left associative and set to value 8, which is one less then
of function composition (`.`

).

(~@@~>) :: Functor f => (a -> b) -> (c -> d) -> (b -> f c) -> a -> f dSource

Flipped variant of `<~@~`

.

This function allows us to define lenses mostly for pair of functions that form an isomorphism. See section Constructing Lenses for details.

Name of `~@@~>`

simply says that we apply `<$>`

(`fmap`

) to
second (right) argument and then we apply `~@@~`

.

Fixity is right associative and set to value 8, which is one less then
fixity of function composition (`.`

).

(~@~>) :: Functor f => (c -> d) -> (a -> b) -> (f b -> c) -> f a -> dSource

Apply fmap to second argument of `~@~`

. Dual to `<~@~`

which applies
`fmap`

to first argument.

Defined as:

Name of `~@~>`

simply says that we apply `<$>`

(`fmap`

) to
second (right) argument and then we apply `~@~`

.

Fixity is right associative and set to value 8, which is one less then
of function composition (`.`

).

(<^@~) :: Functor f => (a -> c -> d) -> (a -> b) -> (b -> f c) -> a -> f dSource

Convenience wrapper for: `\f g -> `

.
`fmap`

. f `^@~`

g

This function has the same functionality as function

lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b

Which is defined in lens package.
Only difference is that arguments of `<^@~`

are flipped. See also section
Constructing Lenses.

Name of `<^@~`

simply says that we apply `<$>`

(`fmap`

) to
first (left) arguments and then we apply `^@~`

.

Fixity is left associative and set to value 8, which is one less then
of function composition (`.`

).

(~@@^>) :: Functor f => (a -> b) -> (a -> c -> d) -> (b -> f c) -> a -> f dSource

Flipped variant of `~@^>`

.

This function has the same functionality as function

lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b

Which is defined in lens package. See also section Constructing Lenses.

Name of `~@^>`

simply says that we apply `<$>`

(`fmap`

) to
second (right) arguments and then we apply `~@^>`

.

Fixity is left associative and set to value 8, which is one less then
of function composition (`.`

).

(<^@^>) :: (Functor f, Functor g) => (a -> d -> e) -> (a -> b -> c) -> (f c -> g d) -> a -> f b -> g eSource

(<^@@^>) :: (Functor f, Functor g) => (a -> b -> c) -> (a -> d -> e) -> (f c -> g d) -> a -> f b -> g eSource

(<^@^) :: Functor f => (a -> d -> e) -> (a -> b -> c) -> (c -> f d) -> a -> b -> f eSource

Convenience wrapper for: `\f g -> `

.
`fmap`

. f `^@^`

g

This function allows us to define generic lenses from gettern and setter. See section Constructing Lenses for details.

Name of `<^@^`

simply says that we apply `<$>`

(`fmap`

) to
first (left) arguments and then we apply `^@^`

.

Fixity is left associative and set to value 8, which is one less then
of function composition (`.`

).

(^@@^>) :: Functor f => (a -> b -> c) -> (a -> d -> e) -> (c -> f d) -> a -> b -> f eSource

Flipped variant of `<^@^`

.

This function allows us to define generic lenses from gettern and setter. See section Constructing Lenses for details.

Name of `^@@^>`

simply says that we apply `<$>`

(`fmap`

) to
second (right) arguments and then we apply `^@@^`

.

Fixity is set to value 8, which is one less then of function composition
(`.`

).