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(,,).map(x => x) assert_=== List(,,)

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

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

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

 scala> Functor[List].map(List(,,).map(i => i + ))(i2 => i2 * ) assert_=== List(,,).map(((i:Int) => i + ) compose ((i2:Int) => i2 * ))
java.lang.RuntimeException: [,,] ≠ [,,]
at scala.sys.package$.error(package.scala:)
at scalaz.syntax.EqualOps.assert_$eq$eq$eq(EqualSyntax.scala:)
... elided

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

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

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

 case class Item3[A](i1: A, i2: A, i3: A)
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

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

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

看来我们需要提供自定义类型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 tests.
+ functor.invariantFunctor.composite: OK, passed tests.
+ functor.identity: OK, passed tests.
+ functor.composite: OK, passed 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) + ) map((k: Int) => k * ))()
res20: Int = scala> (((_: Int) + ) map((_: Int) * ))()
res21: Int = scala> (((_: Int) + ) andThen ((_: Int) * ))()
res22: Int = scala> (((_: Int) * ) compose ((_: Int) + ))()
res23: Int =

我们也可以对Functor进行compose:

 scala> val f = Functor[List] compose Functor[Item3]
f: scalaz.Functor[[α]List[Item3[α]]] = scalaz.Functor$$anon$@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(,,), Item3(,,))

反过来操作:

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

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

 scala> item3.fpair
res28: Item3[(String, String)] = Item3((Morning,Morning),(Noon,Noon),(Night,Night)) scala> item3.strengthL()
res29: Item3[(Int, String)] = Item3((,Morning),(,Noon),(,Night)) scala> item3.strengthR()
res30: Item3[(String, Int)] = Item3((Morning,),(Noon,),(Night,)) scala> item3.fproduct(_.length)
res31: Item3[(String, Int)] = Item3((Morning,),(Noon,),(Night,)) 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(,,))(_ + )
res35: List[Int] = List(, , ) scala> Functor[Option].map(Some())(_ + )
res36: Option[Int] = Some() scala> Functor[java.util.concurrent.Callable]
res37: scalaz.Functor[java.util.concurrent.Callable] = scalaz.std.java.util.concurrent.CallableInstances$$anon$@4176ab89 scala> Functor[Stream]
res38: scalaz.Functor[Stream] = scalaz.std.StreamInstances$$anon$@4f5374b9 scala> Functor[Vector]
res39: scalaz.Functor[Vector] = scalaz.std.IndexedSeqSubInstances$$anon$@4367920a

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

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

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

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

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

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

 cala> Functor[({type l[x] = (String,x)})#l].map(("a",))(_ + )
res57: (String, Int) = (a,) scala> Functor[({type l[x] = (String,Int,x)})#l].map(("a",,"b"))(_.toUpperCase)
res58: (String, Int, String) = (a,,B) scala> Functor[({type l[x] = (String,Int,Boolean,x)})#l].map(("a",,true,Item3("a","b","c")))(i => i.map(_.toUpperCase))
res62: (String, Int, Boolean, Item3[String]) = (a,,true,Item3(A,B,C))

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

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

    Applicative,正如它的名称所示,就是FP模式的函数施用(function application).我们在前面的讨论中不断提到FP模式的操作一般都在管道里进行的,因为FP的变量表达形式是这样 ...

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

    中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾 ...

  3. Scalaz(22)- 泛函编程思维: Coerce Monadic Thinking

    马上进入新的一年2016了,来点轻松点的内容吧.前面写过一篇关于用Reader实现依赖注入管理的博文(Scalaz(16)- Monad:依赖注入-Dependency Injection By Re ...

  4. Scalaz(53)- scalaz-stream: 程序运算器-application scenario

    从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...

  5. MySQL数据分析-(15)表补充:存储引擎

    大家好,我是jacky,很高兴继续跟大家分享<MySQL数据分析实战>,今天跟大家分享的主题是表补充之存储引擎: 我们之前学了跟表结构相关的一些操作,那我们看一下创建表的SQL模型: 在我 ...

  6. STL笔记(6)标准库:标准库中的排序算法

    STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew A ...

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

    随着计算机的发展.普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号.为了解决这种不兼容的问题 ...

  8. Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition

    Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition Property animation系统还提供了对ViewGroup中的View改变 ...

  9. Android Animation学习(四) ApiDemos解析:多属性动画

    Android Animation学习(四) ApiDemos解析:多属性动画 如果想同时改变多个属性,根据前面所学的,比较显而易见的一种思路是构造多个对象Animator , ( Animator可 ...

随机推荐

  1. MyBatis学习总结(一)——MyBatis快速入门

    一.Mybatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以 ...

  2. Atitit 基于图片图像 与文档混合文件夹的分类

    Atitit 基于图片图像 与文档混合文件夹的分类 太小的文档(txt doc csv exl ppt pptx)单独分类 Mov10KminiDoc 但是可能会有一些书法图片迁移,因为他们很微小,需 ...

  3. iOS---用LLDB调试,让移动开发更简单(一)

    因文章字数超过限制,所以拆分成了上下篇 LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用 ...

  4. Hadoop学习笔记【Hadoop家族成员概述】

    Hadoop家族成员概述 一.Hadoop简介 1.1 什么是Hadoop? Hadoop是一个分布式系统基础架构,由Apache基金会所开发,目前Yahoo!是其最重要的贡献者. Hadoop实现了 ...

  5. 35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n

      35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现in ...

  6. Win10下PB停在欢迎窗口界面

    问题:Win10下不能打开PB12.5,PB12.6,一直停在欢迎窗口界面. 解决方法:把服务"Touch Keyboard and Handwriting Panel Service&qu ...

  7. SSIS Component的ValidateExternalMetadata属性

    ValidateExternalMetadata Property Indicates whether the component validates its column metadata agai ...

  8. Render OpenCascade Geometry Surfaces in OpenSceneGraph

    在OpenSceneGraph中绘制OpenCascade的曲面 Render OpenCascade Geometry Surfaces in OpenSceneGraph eryar@163.co ...

  9. 【转】Windows Phone在隔离存储里存取图片文件

    一共两个页面,第一个为MainPage.xaml,代码如下: <!--ContentPanel - place additional content here--> <Grid x: ...

  10. JAVA实现Excel的读写--jxl

    前段时间因为开发网站的需要,研究了一下java实现excel的读写,一般当我们做管理软件时,都需要打印报表,报表如何制作呢?相信一定难为过大家,本篇就为大家揭开它的神秘面纱,学习完半篇,你一定会对报表 ...