Scala - Как использовать функторы на не-функциональных типах? - PullRequest
8 голосов
/ 27 сентября 2011

Читая описание Функторов в этом блоге:

https://hseeberger.wordpress.com/2010/11/25/introduction-to-category-theory-in-scala/

есть общее определение Functor и более конкретное:

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

Очевидно, это означает, что функторы могут использоваться с другими типами с более высоким родом, кроме объектов Function. Может ли кто-нибудь привести пример или объяснить, как, почему или в каком сценарии это будет сделано? А именно, какой будет другая реализация GenericFunctor в Scala, которая использует конструктор типов, отличный от Function? Спасибо!

EDIT:

Просто чтобы уточнить:

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)

Просто чтобы уточнить, согласно моему пониманию, ListFunctor реализует 1-arg fmap в GenericFunctor, в то время как код в транскрипте repl вызывает fmap в Trait Functor, который, в свою очередь, вызывает реализацию fmap (например, в ListFunctor).

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

Ответы [ 3 ]

7 голосов
/ 28 сентября 2011

В вашем примере Functor является endofunctor в категории типов Scala с Function1 в виде стрелок.

Существуют и другие категории.Например, представьте категорию, в которой объекты являются типами Scala, и есть стрелка A >~> B, если B является подтипом A.Эта категория в Scalaz называется Liskov.Существует «забывчивый» функтор из категории Liskov в категорию Function1:

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

Обратите внимание, что вы можете создать некоторые интересные функторы, зафиксировав один или несколько аргументов в GenericFunctor.Например ...

A константный функтор отображает каждый объект в одной категории на один объект в другой:

type ConstantFunctor[->>[_, _], ->>>[_, _], C] =
  GenericFunctor[->>,->>>,({type F[x] = C})#F]
// def fmap[A, B](f: A ->> B): C ->>> C

endofunctor сопоставляет категорию с самим собой:

type EndoFunctor[->>[_, _], F[_]] = GenericFunctor[->>, ->>, F]
// def fmap[A, B](f: A ->> B): F[A] ->> F[B]

функтор идентификации отображает каждый объект и стрелку на себя:

type IdentityFunctor[->>[_, _]] = EndoFunctor[->>, ({type F[x] = x})#F]
// def fmap[A, B](f: A ->> B): A ->> B

И, конечно, ваша Functor чертаэто просто EndoFunctor в категории Function1.

type Functor[F[_]] = EndoFunctor[Function1, F]
// def fmap[A, B](f: A => B): F[A] => F[B]
6 голосов
/ 27 сентября 2011

Вы можете представить себе функтор, который поднимает экземпляр Either[A,B] в Either[F[A],F[B]], где F может быть List, Option и т. Д.

РЕДАКТИРОВАТЬ Пример реализации:

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 Другой (возможно, полезный) пример - функтор с переходом от PartialFunction (тип ->>) к Function (тип ->>>):

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)

}

Этот второй пример позволяет реализовать операцию collect, как определено в коллекциях Scala:

def collect[A,B,F[_]]( as: F[A] )
                     ( pf: PartialFunction[A,B] )
                     ( implicit functor: PartialFunctor[F] ) = 
  functor.fmap( as )( pf )
4 голосов
/ 27 сентября 2011

Data.Category - хороший источник примеров подобных вещей (в Хаскеле).Частично перевод одного из экземпляров из 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)
}
...