开发者

Scala -- How to use Functors on non-Function types?

开发者 https://www.devze.com 2023-04-08 07:52 出处:网络
While reading the description of Functors on this blog: https://hseeberger.wordpress.com/2010/11/25/introduction-to-category-theory-in-scala/

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)
}
0

精彩评论

暂无评论...
验证码 换一张
取 消