Functional programming
Let's start with some definitions: (1)(5)(8)
Covariant Functor — defines the operation commonly known as map or fmap.
map ( A=>B ) => ( C[A]=>C[B] ) or map C[A] => (A=>B) => C[B]
Monad: — defines the operation commonly known as bind, flatMap or =<<.
flatMap ( A=>C[B] ) => ( C[A]=>C[B] )
insert A => C[A]
Monads also define an identity operation, insert
Applicative Functor — defines the operation commonly known as apply or *.
apply ( C[A=>B] ) => ( C[A]=>C[B] )
insert A => C[A]
Applicative functors also define an identity operation, insert
Exponential Functors — defines the operation commonly known as xmap. Also known as invariant functors.
xmap ( (A => B, B => A) ) => ( F[A] => F[B] )
Contravariant Functor — defines the operation commonly known as contramap.
contramap ( B=>A ) => ( C[A]=>C[B] )
Comonad — defines the operation commonly known as extend, coflatMap or <<=.
coflatMap ( F[A] => B ) => ( F[A] => F[B] )
extract F[A] => A
Comonads also define an identity operation, extract
Implementation details:
Let's assume that we have
class Container[T](val value:T)
// Covariant Functor (1)(2)(3)
def map[A,B] ( rawfunc:A=>B ) = (a:Container[A]) => new Container( rawfunc(a.value) )
or
def map[A,B](a:Container[A]): (A=>B)=>Container[B] = (rawfunc:A=>B) => new Container( rawfunc(a.value) )
or
class Container[A](val value:A) {
def map[A,B](rawfunc:A=>B) = new Container( rawfunc(this.value) )
}
or
implicit class ContainerWrapper[T](w:Container[T]) {
def flatMap[R](f:T=>Container[R]) = map(f).value // monad
def map[R](f:T=>R) = new Container(f(w.value)) // functor
}
or (5)
trait Functor[T[_]] {
def fmap[A,B](f: A=>B)(ta: T[A]): T[B]
}
def flatMap[A,B]( func:A=>Container[B] ) = (a:Container[A]) => func( a.value )
or
def flatMap[A,B](func:A=>Container[B]) = (a:Container[A]) => map(func)(a).value
or
def flatMap[A,B](func:A=>MyBox[B]) = (a:MyBox[A]) => flatten(map(func)(a))
def flatten[B](m:MyBox[MyBox[B]]):MyBox[B] = m.value
or (5)
trait Monad[M[_]] extends Applicative[M] {
def >>=[A,B](ma:M[A])(f:A=>M[B]):M[B] // or bind
}
// Applicative (3)
def apply[A,B] ( b:Container[A=>B] ) = (a:Container[A]) => new Container(b.value(a.value))
or (5)
trait Applicative[T[_]] extends Functor[T] {
def pure[A](a:A):T[A] // or identity
def <*>[A,B](tf:T[A=>B])(ta:T[A]):T[B] // or apply
}
or (7)
val applicative: F[Y => Z] => F[Y] => F[Z] =
f => a => for {
ff <- f
aa <- a
} yield ff(aa)
fib.zip(fib.tail)
can be rewritten as zip <*> tail
// Exponential functor (8)
trait Exponential[F[_]] {
def xmap[A, B](f: (A => B, B => A)): F[A] => F[B]
}
// Contravariant functor (8)
trait Contravariant[F[_]] {
def contramap[A, B](f: B => A): F[A] => F[B]
}
// Comonad (8)
trait Comonad[F[_]] {
def coflatMap[A, B](f: F[A] => B): F[A] => F[B]
def extract[A]: F[A] => A
}
Concrete use:
Writer monad: (4)
class LogBox[T](val value:T, val mesg:String="") {
def map[B](f:T=>B) = new LogBox(f(value), mesg)
def flatMap[B](f:T=>LogBox[B]) = flatten( map(f) )
def flatten[B](m:LogBox[LogBox[B]]) = new LogBox( m.value.value, m.mesg+m.value.mesg+"\n")
}
References:
- Functors, Monads, Applicatives – can be so simple
- Functors, Monads, Applicatives – different implementations
- Functors, Monads, Applicatives – playing with map (Functor)
- Functors, Monads, Applicatives – taking Monad apart
- Functors, Applicative Functors, and Monads aren't that scary
- Haskell Typeclassopedia
- Case study of fib.zip(fib.tail) by Tony Morris
- Functors and things using Scala