Scala -- How to use Functors on non-Function types?
While reading the description of Functors on this blog:
https://hseeberger.wordpress.com/2010/11/25/introduction-to-category-theory-in-scala/
there is a generic definition of Functor and a more specific one:
trait GenericFunctor[->>[_, _], ->>>[_, _], F[_]] {
def fmap[A, B](f: A ->> B): F[A] ->>> F[B]
}
trait Functor[F[_]] extends GenericFunctor[Function, Function, F] {
final def fmap[A, B](as: F[A])(f: A => B): F[B] =
fmap(f)(as)
}
Clearly this means Functors can be used with other higher-kinded types besides Function objects. Could someone pleas开发者_如何转开发e give an example or explain how or why or in what scenario that would be done? Namely, what would another implementation of GenericFunctor be in Scala -- that uses a different type constructor from Function? Thanks!
EDIT:
Just to clarify:
object Functor {
def fmap[A, B, F[_]](as: F[A])(f: A => B)(implicit functor: Functor[F]): F[B] =
functor.fmap(as)(f)
implicit object ListFunctor extends Functor[List] {
def fmap[A, B](f: A => B): List[A] => List[B] =
as => as map f
}
}
scala> fmap(List(1, 2, 3))(x => x + 1)
res0: List[Int] = List(2, 3, 4)
Just to clarify, according to my understanding ListFunctor implements the 1-arg fmap in GenericFunctor while the code in the repl transcript calls the fmap in Trait Functor, which in turn calls an fmap implementation (e.g. in ListFunctor).
This doesn't change the overall question, just thought it would help people trying to provide answers. Any insights provided would be appreciated.
In your example Functor
is an endofunctor in the category of Scala types with Function1
as arrows.
There are other categories. For example, imagine a category in which the objects are Scala types, and there is an arrow A >~> B
if B
is a subtype of A
. This category in Scalaz is called Liskov
. There is a "forgetful" functor from the Liskov
category to the Function1
category:
import scalaz._
import Scalaz._
trait Forget[F[-_]] extends GenericFunctor[>~>, Function1, F] {
def fmap[A, B](f: A >~> B): F[A] => F[B] = fa => f.subst(fa)
}
Note that you can build some interesting functors by fixing one or more of the arguments to GenericFunctor
. For example...
A constant functor maps every object in one category to a single object in another:
type ConstantFunctor[->>[_, _], ->>>[_, _], C] =
GenericFunctor[->>,->>>,({type F[x] = C})#F]
// def fmap[A, B](f: A ->> B): C ->>> C
An endofunctor maps a category to itself:
type EndoFunctor[->>[_, _], F[_]] = GenericFunctor[->>, ->>, F]
// def fmap[A, B](f: A ->> B): F[A] ->> F[B]
An identity functor maps every object and arrow to itself:
type IdentityFunctor[->>[_, _]] = EndoFunctor[->>, ({type F[x] = x})#F]
// def fmap[A, B](f: A ->> B): A ->> B
And of course, your Functor
trait is just an EndoFunctor
in the Function1
category.
type Functor[F[_]] = EndoFunctor[Function1, F]
// def fmap[A, B](f: A => B): F[A] => F[B]
You can imagine a functor which lifts an instance of Either[A,B]
into an Either[F[A],F[B]]
where F
can be a List
, Option
, etc.
EDIT Implementation example:
trait GenericFunctor[->>[_, _], ->>>[_, _], F[_]] {
def fmap[A, B](f: A ->> B): F[A] ->>> F[B]
}
trait EitherFunctor[F[_]] extends GenericFunctor[Either,Either,F]
object ListFunctor extends EitherFunctor[List] {
def fmap[A,B]( f: Either[A,B] ): Either[List[A],List[B]] =
f match {
case Left(a) => Left( List(a) )
case Right(b) => Right( List(b) )
}
}
EDIT2 Another (maybe useful) example is with a functor with goes from a PartialFunction
(type ->>
) to a Function
(type ->>>
):
trait PartialFunctor[F[_]]
extends GenericFunctor[PartialFunction,Function,F] {
final def fmap[A, B](as: F[A])(f: PartialFunction[A,B]): F[B] =
fmap(f)(as)
}
object OptionFunctor extends PartialFunctor[Option] {
def fmap[A,B]( f: PartialFunction[A,B] ): Option[A] => Option[B] =
(opt:Option[A]) => opt match {
case Some(a) => f.lift(a)
case None => None
}
}
object ListFunctor extends PartialFunctor[List] {
private def mapPartial[A,B]( f: PartialFunction[A,B], as: List[A] ): List[B] =
as match {
case Nil => Nil
case h :: t => if( f isDefinedAt h ) f(h) :: mapPartial( f, t )
else mapPartial( f, t )
}
def fmap[A,B]( f: PartialFunction[A,B] ): List[A] => List[B] =
(lst:List[A]) => mapPartial(f, lst)
}
This second example allows to implement the collect
operation as defined in Scala collections:
def collect[A,B,F[_]]( as: F[A] )
( pf: PartialFunction[A,B] )
( implicit functor: PartialFunctor[F] ) =
functor.fmap( as )( pf )
Data.Category
is a good source for examples of things like this (in Haskell). Partially translating one of the instances from http://hackage.haskell.org/packages/archive/data-category/0.4.1/doc/html/src/Data-Category-Functor.html:
type OpFun[A, B] = B => A
case class OpFunctor[F[_]](functor: Functor[F[_]]) extends GenericFunctor[OpFun, OpFun, F] {
def fmap[A, B](f: B => A): F[B] => F[A] = fb => functor.fmap(fb)(f)
}
精彩评论