Scalaz(6)- typeclass:Functor-just map

  Functor是范畴学(Category theory)里的概念。不过无须担心,我们在scala FP编程里并不需要先掌握范畴学知识的。在scalaz里,Functor就是一个普通的typeclass,具备map over特性。我的理解中,Functor的主要用途是在FP过程中更新包嵌在容器(高阶类)F[T]中元素T值。典型例子如:List[String], Option[Int]等。我们曾经介绍过FP与OOP的其中一项典型区别在于FP会尽量避免中间变量(temp variables)。FP的变量V是以F[V]这种形式存在的,如:List[Int]里一个Int变量是包嵌在容器List里的。所以FP需要特殊的方式来更新变量V,这就是Functor map over的意思。scalaz提供了Functor typeclass不但使用户能map over自定义的高阶类型F[T],并且用户通过提供自定义类型的Functor实例就可以免费使用scalaz Functor typeclass提供的一系列组件函数(combinator functions)。

  scalaz中Functor的trait是这样定义的:scalaz/Functor.scala

trait Functor[F[_]] extends InvariantFunctor[F] { self =>
  ////
  import Liskov.<~<

  /** Lift `f` into `F` and apply to `F[A]`. */
  def map[A, B](fa: F[A])(f: A => B): F[B]

...

任何类型的实例只需要实现这个抽象函数map就可以使用scalaz Functor typeclass的这些注入方法了:scalaz/syntax/FunctorSyntax.scala

final class FunctorOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Functor[F]) extends Ops[F[A]] {
  ////
  import Leibniz.===
  import Liskov.<~<

  final def map[B](f: A => B): F[B] = F.map(self)(f)
  final def distribute[G[_], B](f: A => G[B])(implicit D: Distributive[G]): G[F[B]] = D.distribute(self)(f)
  final def cosequence[G[_], B](implicit ev: A === G[B], D: Distributive[G]): G[F[B]] = D.distribute(self)(ev(_))
  final def cotraverse[G[_], B, C](f: F[B] => C)(implicit ev: A === G[B], D: Distributive[G]): G[C] = D.map(cosequence)(f)
  final def ∘[B](f: A => B): F[B] = F.map(self)(f)
  final def strengthL[B](b: B): F[(B, A)] = F.strengthL(b, self)
  final def strengthR[B](b: B): F[(A, B)] = F.strengthR(self, b)
  final def fpair: F[(A, A)] = F.fpair(self)
  final def fproduct[B](f: A => B): F[(A, B)] = F.fproduct(self)(f)
  final def void: F[Unit] = F.void(self)
  final def fpoint[G[_]: Applicative]: F[G[A]] = F.map(self)(a => Applicative[G].point(a))
  final def >|[B](b: => B): F[B] = F.map(self)(_ => b)
  final def as[B](b: => B): F[B] = F.map(self)(_ => b)
  final def widen[B](implicit ev: A <~< B): F[B] = F.widen(self)
  ////
}

以上的注入方法中除了map外其它方法的应用场景我还没有确切的想法,不过这不会妨碍我们示范它们的用法。Functor必须遵循一些定律:

1、map(fa)(x => x) === fa

2、map(map(fa)(f1))(f2) === map(fa)(f2 compose f1)

scalaz/Functor.scala

trait FunctorLaw extends InvariantFunctorLaw {
    /** The identity function, lifted, is a no-op. map(fa)(x => x*/
    def identity[A](fa: F[A])(implicit FA: Equal[F[A]]): Boolean = FA.equal(map(fa)(x => x), fa)

    /**
     * A series of maps may be freely rewritten as a single map on a
     * composed function.
     */
    def composite[A, B, C](fa: F[A], f1: A => B, f2: B => C)(implicit FC: Equal[F[C]]): Boolean = FC.equal(map(map(fa)(f1))(f2), map(fa)(f2 compose f1))
  }

我们可以用List来证明:map(fa)(x => x) === fa

scala> List(1,2,3).map(x => x) assert_=== List(1,2,3)

scala> List(1,2,3).map(identity) assert_=== List(1,2)
java.lang.RuntimeException: [1,2,3] ≠ [1,2]
  at scala.sys.package$.error(package.scala:27)
  at scalaz.syntax.EqualOps.assert_$eq$eq$eq(EqualSyntax.scala:16)
  ... 43 elided

map(map(fa)(f1))(f2) === map(fa)(f2 compose f1)

scala> Functor[List].map(List(1,2,3).map(i => i + 1))(i2 => i2 * 3) assert_=== List(1,2,3).map(((i2:Int) => i2 * 3) compose ((i:Int) => i + 1))

scala> Functor[List].map(List(1,2,3).map(i => i + 1))(i2 => i2 * 3) assert_=== List(1,2,3).map(((i:Int) => i + 1) compose ((i2:Int) => i2 * 3))
java.lang.RuntimeException: [6,9,12] ≠ [4,7,10]
  at scala.sys.package$.error(package.scala:27)
  at scalaz.syntax.EqualOps.assert_$eq$eq$eq(EqualSyntax.scala:16)
  ... 43 elided

注意:compose对f1,f2的施用是互换的。

针对我们自定义的类型,我们只要实现map函数就可以得到这个类型的Functor实例。一旦实现了这个类型的Functor实例,我们就可以使用以上scalaz提供的所有Functor组件函数了。

我们先试着创建一个类型然后推算它的Functor实例:

1 case class Item3[A](i1: A, i2: A, i3: A)
2 val item3Functor = new Functor[Item3] {
3     def map[A,B](ia: Item3[A])(f: A => B): Item3[B] = Item3(f(ia.i1),f(ia.i2),f(ia.i3))
4 }                                                 //> item3Functor  : scalaz.Functor[scalaz.functor.Item3] = scalaz.functor$$anonf
5                                                   //| un$main$1$$anon$1@5e265ba4

scalaz同时在scalaz-tests下提供了一套scalacheck测试库。我们可以对Item3的Functor实例进行测试:

1 scala> functor.laws[Item3].check
2 <console>:27: error: could not find implicit value for parameter af: org.scalacheck.Arbitrary[Item3[Int]]
3               functor.laws[Item3].check
4                           ^

看来我们需要提供自定义类型Item3的随意产生器(Generator):

scala> implicit def item3Arbi[A](implicit a: Arbitrary[A]): Arbitrary[Item3[A]] = Arbitrary {
     | def genItem3: Gen[Item3[A]]  = for {
     | b <- Arbitrary.arbitrary[A]
     | c <- Arbitrary.arbitrary[A]
     | d <- Arbitrary.arbitrary[A]
     | } yield Item3(b,c,d)
     | genItem3
     | }
item3Arbi: [A](implicit a: org.scalacheck.Arbitrary[A])org.scalacheck.Arbitrary[Item3[A]]

scala> functor.laws[Item3].check
+ functor.invariantFunctor.identity: OK, passed 100 tests.
+ functor.invariantFunctor.composite: OK, passed 100 tests.
+ functor.identity: OK, passed 100 tests.
+ functor.composite: OK, passed 100 tests.

Item3的Functor实例是合理的。

实际上map就是(A => B) => (F[A] => F[B]),就是把(A => B)升格(lift)成(F[A] => F[B]):

case class Item3[A](i1: A, i2: A, i3: A)
implicit val item3Functor = new Functor[Item3] {
    def map[A,B](ia: Item3[A])(f: A => B): Item3[B] = Item3(f(ia.i1),f(ia.i2),f(ia.i3))
}                                                 //> item3Functor  : scalaz.Functor[scalaz.functor.Item3] = scalaz.functor$$anonf
                                                  //| un$main$1$$anon$1@5e265ba4
val F = Functor[Item3]                            //> F  : scalaz.Functor[scalaz.functor.Item3] = scalaz.functor$$anonfun$main$1$$
                                                  //| anon$1@5e265ba4
F.map(Item3("Morning","Noon","Night"))(_.length)  //> res0: scalaz.functor.Item3[Int] = Item3(7,4,5)
F.apply(Item3("Morning","Noon","Night"))(_.length)//> res1: scalaz.functor.Item3[Int] = Item3(7,4,5)
F(Item3("Morning","Noon","Night"))(_.length)      //> res2: scalaz.functor.Item3[Int] = Item3(7,4,5)
F.lift((s: String) => s.length)(Item3("Morning","Noon","Night"))
                                                  //> res3: scalaz.functor.Item3[Int] = Item3(7,4,5)

虽然函数升格(function lifting (A => B) => (F[A] => F[B])是Functor的主要功能,但我们说过:一旦能够获取Item3类型的Functor实例我们就能免费使用所有的注入方法:

scalaz提供了Function1的Functor实例。Function1 Functor的map就是 andThen 也就是操作方调换的compose:

scala> (((_: Int) + 1) map((k: Int) => k * 3))(2)
res20: Int = 9

scala> (((_: Int) + 1) map((_: Int) * 3))(2)
res21: Int = 9

scala> (((_: Int) + 1) andThen ((_: Int) * 3))(2)
res22: Int = 9

scala> (((_: Int) * 3) compose ((_: Int) + 1))(2)
res23: Int = 9

我们也可以对Functor进行compose:

scala> val f = Functor[List] compose Functor[Item3]
f: scalaz.Functor[[α]List[Item3[α]]] = scalaz.Functor$$anon$1@647ce8fd

scala> val item3 = Item3("Morning","Noon","Night")
item3: Item3[String] = Item3(Morning,Noon,Night)

scala> f.map(List(item3,item3))(_.length)
res25: List[Item3[Int]] = List(Item3(7,4,5), Item3(7,4,5))

反过来操作:

1 scala> val f1 = Functor[Item3] compose Functor[List]
2 f1: scalaz.Functor[[α]Item3[List[α]]] = scalaz.Functor$$anon$1@5b6a0166
3
4 scala> f1.map(Item3(List("1"),List("22"),List("333")))(_.length)
5 res26: Item3[List[Int]] = Item3(List(1),List(2),List(3))

我们再试着在Item3类型上调用那些免费的注入方法:

scala> item3.fpair
res28: Item3[(String, String)] = Item3((Morning,Morning),(Noon,Noon),(Night,Night))

scala> item3.strengthL(3)
res29: Item3[(Int, String)] = Item3((3,Morning),(3,Noon),(3,Night))

scala> item3.strengthR(3)
res30: Item3[(String, Int)] = Item3((Morning,3),(Noon,3),(Night,3))

scala> item3.fproduct(_.length)
res31: Item3[(String, Int)] = Item3((Morning,7),(Noon,4),(Night,5))

scala> item3 as "Day"
res32: Item3[String] = Item3(Day,Day,Day)

scala> item3 >| "Day"
res33: Item3[String] = Item3(Day,Day,Day)

scala> item3.void
res34: Item3[Unit] = Item3((),(),())

我现在还没有想到这些函数的具体用处。不过从运算结果来看,用这些函数来产生一些数据模型用在游戏或者测试的模拟(simulation)倒是可能的。

scalaz提供了许多现成的Functor实例。我们先看看一些简单直接的实例:

scala> Functor[List].map(List(1,2,3))(_ + 3)
res35: List[Int] = List(4, 5, 6)

scala> Functor[Option].map(Some(3))(_ + 3)
res36: Option[Int] = Some(6)

scala> Functor[java.util.concurrent.Callable]
res37: scalaz.Functor[java.util.concurrent.Callable] = scalaz.std.java.util.concurrent.CallableInstances$$anon$1@4176ab89

scala> Functor[Stream]
res38: scalaz.Functor[Stream] = scalaz.std.StreamInstances$$anon$1@4f5374b9

scala> Functor[Vector]
res39: scalaz.Functor[Vector] = scalaz.std.IndexedSeqSubInstances$$anon$1@4367920a

对那些多个类型变量的类型我们可以采用部分施用方式:即type lambda来表示。一个典型的类型:Either[E,A],我们可以把Left[E]固定下来: Either[String, A],我们可以用type lambda来这样表述:

1 scala> Functor[({type l[x] = Either[String,x]})#l].map(Right(3))(_ + 3)
2 res41: scala.util.Either[String,Int] = Right(6)

如此这般我可以对Either类型进行map操作了。

函数类型的Functor是针对返回类型的:

scala> Functor[({type l[x] = String => x})#l].map((s: String) => s + "!")(_.length)("Hello")
res53: Int = 6

scala> Functor[({type l[x] = (String,Int) => x})#l].map((s: String, i: Int) => s.length + i)(_ * 10)("Hello",5)
res54: Int = 100

scala> Functor[({type l[x] = (String,Int,Boolean) => x})#l].map((s: String,i: Int, b: Boolean)=> s + i.toString + b.toString)(_.toUpperCase)("Hello",3,true)
res56: String = HELLO3TRUE

tuple类型的Functor是针对最后一个元素类型的: 

cala> Functor[({type l[x] = (String,x)})#l].map(("a",1))(_ + 2)
res57: (String, Int) = (a,3)

scala> Functor[({type l[x] = (String,Int,x)})#l].map(("a",1,"b"))(_.toUpperCase)
res58: (String, Int, String) = (a,1,B)

scala> Functor[({type l[x] = (String,Int,Boolean,x)})#l].map(("a",1,true,Item3("a","b","c")))(i => i.map(_.toUpperCase))
res62: (String, Int, Boolean, Item3[String]) = (a,1,true,Item3(A,B,C))
时间: 2024-09-14 01:55:31

Scalaz(6)- typeclass:Functor-just map的相关文章

Scalaz(7)- typeclass:Applicative-idomatic function application

   Applicative,正如它的名称所示,就是FP模式的函数施用(function application).我们在前面的讨论中不断提到FP模式的操作一般都在管道里进行的,因为FP的变量表达形式是这样的:F[A],即变量A是包嵌在F结构里的.Scalaz的Applicative typeclass提供了各种类型的函数施用(function application)和升格(lifting)方法.与其它scalaz typeclass使用方式一样,我们只需要实现了针对自定义类型的Applica

Scalaz(25)- Monad: Monad Transformer-叠加Monad效果

  中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾经看到一段对Monad的描述:"Monadic for-comprehension就是一种嵌入式编程语言,由它的Monad提供它的语法".但如果每一种Monad的for-comprehension都独立提供一套语法的话,这种编程语言就显得十分单调.功能简单了.那么既然是FP,我们应该可

常(const)+ 对象 + 指针:玻璃罩到底保护哪一个

原创案例讲解--"玻璃罩const"系列的三篇文章: 1. 使用常对象--为共用数据加装一个名为const的玻璃罩 2. 常(const)+ 对象 + 指针:玻璃罩到底保护哪一个 3. 对象更有用的玻璃罩--常引用 在上一篇文章<使用常对象--为共用数据加装一个名为const的玻璃罩>中,利用案例讨论了运用常对象,常成员函数.常数据成员及其用法.const这个玻璃罩让数据只能看,不能改,有效地避免程序免受不该出现的修改(引起bug的元凶)操作的影响. 本文继续讨论const

java中文乱码解决之道(三)—–编码详情:伟大的创想—Unicode编码

随着计算机的发展.普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号.为了解决这种不兼容的问题,伟大的创想Unicode编码应时而生!! Unicode Unicode又称为统一码.万国码.单一码,它是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言.跨平台进行文本转换.处理的要求.可以想象Unicode作为一个"字符大容器&qu

ASP.NET讨论(上海)QQ群:216066174

问题描述 ASP.NET讨论(上海)QQ群:216066174,非在上海勿加,专注行业管理软件趋势研究与开发 解决方案 解决方案二:欢迎各位在上海的人士加入,共同探讨行业管理软件发展趋势

Linux中断(interrupt)子系统之一:中断系统基本原理【转】

转自:http://blog.csdn.net/DroidPhone/article/details/7445825 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 设备中断控制器和CPU IRQ编号 在驱动程序中申请中断 通用中断子系统Generic irq的软件抽象 irq描述结构struct irq_desc 中断子系统的proc文件接口 这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区

Linux中断(interrupt)子系统之四:驱动程序接口层 &amp; 中断通用逻辑层【转】

转自:http://blog.csdn.net/droidphone/article/details/7497787 在本系列文章的第一篇:Linux中断(interrupt)子系统之一:中断系统基本原理,我把通用中断子系统分为了4个层次,其中的驱动程序接口层和中断通用逻辑层的界限实际上不是很明确,因为中断通用逻辑层的很多接口,既可以被驱动程序使用,也可以被硬件封装层使用,所以我把这两部分的内容放在一起进行讨论. 本章我将会讨论这两层对外提供的标准接口和内部实现机制,几乎所有的接口都是围绕着ir

Scalaz(9)- typeclass:checking instance abiding the laws

  在前几篇关于Functor和Applilcative typeclass的讨论中我们自定义了一个类型Configure,Configure类型的定义是这样的: 1 case class Configure[+A](get: A) 2 object Configure { 3 implicit val configFunctor = new Functor[Configure] { 4 def map[A,B](ca: Configure[A])(f: A => B): Configure[B

Scalaz(4)- typeclass:标准类型-Equal,Order,Show,Enum

  Scalaz是由一堆的typeclass组成.每一个typeclass具备自己特殊的功能.用户可以通过随意多态(ad-hoc polymorphism)把这些功能施用在自己定义的类型上.scala这个编程语言借鉴了纯函数编程语言Haskell的许多概念.typeclass这个名字就是从Haskell里引用过来的.只不过在Haskell里用的名称是type class两个分开的字.因为scala是个OOP和FP多范畴语言,为了避免与OOP里的type和class发生混扰,所以就用了typecl