之前已经实现了见过了很多基本的type classes,接下来看一看Functor typeclass。如果你不懂什么是Functor可以理解成一个包含map函数的interface,不过其实它应该还要满足一些性质。基本从这里以后的内容都没有相应的背景知识介绍。想学习一个的可以看这里

Functor

先看看它是怎么实现的

1
2
3
4
trait Functor[F[_]] { self =>
/×× Lift `f` into `F` and apply to `F[A]` */
def map[A, B](fa: F[A])(f: A => B): F[B]
}

然后,实现方法的注入

1
2
3
trait FunctorOps[F[_], A] private[syntax](val self: F[A])(implicit val f: Functor[F]) extends Ops[F[A]] {
final def map[B](f: A => B): F[B] = F.map(self)(f)
}

它像Functor里面注入了map方法,我们经常见容器中的map

1
2
3
4
List(1,2,3) map {_ + 1}
// res: List[Int] = List(2,3,4)
(1,2,3) map {_ + 1}
// res: (Int, Int, Int) = (1,2,4)

函数也是Functors

准确的说Function1也是Functor

1
2
3
val f = ((x: Int) => x + 1) map {_ * 3}
f(3)
// res: Int = 12

map这里提供了类似compose的功能。不过使用时有一些不同,

1
2
3
4
5
val f = ((x: Int) => x + 1) map {_ * 3}
f(3) // res: Int = 12
val t = ((x: Int) => x + 1) compose (_ * 3)
t(3) // Error:(12, 44) missing parameter type for expanded function ((x$3) => x$3.$times(3))

Functor其实可以看做范畴的范畴上的态射,其fmap的函数签名为fmap :: (a -> b) -> (r ->a) -> (r -> b),这个正是Function composition,接受一个a->b的函数,以及一个(r -> a)的函数,然后返回一个r->b的函数。

Haskell里面可以

1
fmap (*3) [1,2,3]

scalaz里面可以

1
List(1,2,3) map {3*}

其实fmap可以看做一种lift操作。它把本来对T的操作提升成F[T]的操作。 比如

1
2
ghci> :t fmap (*2)
fmap (*2) :: (Num a, Functor f) => f a -> f a

scalaz里面提供了这个函数,

1
2
val f = Functor[List].lift({(_: Int) * 2})
f(List(1,2,3))

除此以外,scalazFunctor提供其他函数。

1
2
3
4
5
6
List(1,2,3) >| "x"
List(1,2,3) as "X"
List(1,2,3).fpair
List(1,2,3).strengthL("x")
List(1,2,3).strengthR("y")
List(1,2,3).void

Applicative

刚才我们使用的都是只有一个参数的函数,即kind是* -> *,当我们想用多个参数的函数的话,我们就需要一个容器这个容器可以装partially apply以后的内容。这东西我们就叫它Applicative

在Haskell中,Applicative

1
2
3
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b

简单的说,就是pure提供用对a进行打包成盒子f a的功能。而<*>提供从第一个盒子里取出来操作,从第二个盒子里取出来参数,然后返回一个打好包的结果。

scalaz里面

1
2
3
4
trait Applicative[F[_]] extends Apply[F] { self =>
def point[A](a: => A): F[A]
// alias for point
final def pure[A](a: => A): F[A] = point(a)

这里可以看到实现的时候,Applicative继承了Apply这个类型,然后需要实现pure这个函数。通过注入方法,可以通过1.point[Option]这种方法来生成一个Some(1),者通过形如这样的函数实现,利用隐式的参数,来选择使用哪个Applicative的point。

1
def point(implicit F: Applicative[F]): F[A] = Applicative[F].point(self)

HaskellApplicative相比,scalaz的好像少了<*>操作,其实这个函数就在Apply中。

1
2
3
4
5
6
7
8
9
10
11
trait Apply[F[_]] extends Functor[F] { self =>
/** Sequence `f`, then `fa`, combining their results by function
* application.
*
* NB: with respect to `apply2` and all other combinators, as well
* as [[scalaz.Bind]], the `f` action appears to the *left*. So
* `f` should be the "first" `F`-action to perform. This is in
* accordance with all other implementations of this typeclass in
* common use, which are "function first".
*/
def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]

其中<*><*以及*>这些操作都在Apply中提供。

1
2
3
1.some <*> {(_:Int) + 3}.some // res0: Option[Int] = Some(4)
1.some *> 2.some // res1: Option[Int] = Some(2)
none <* 2.some // res2: Option[Nothing] = None

Applicative Style

scalaz还提供了另外一种格式,

1
2
^(1.some, 2.some) {_ + _} // res0: Option[Int] = Some(3)
^(1.some, none[Int]) {_ + _} // res1: Option[Int] = None

这样就不用显示的把函数包裹到Applicative

不过这种语法只能用于这种两个函数的参数,如果你想要多个参数可以使用

1
2
3
val f = (x: Int, y: Int, z: Int) => x + y + z
(3.some |@| 5.some |@| 6.some)(f) // res0: Option[Int] = Some(14)

Lists as Applicative

List本身也是Applicative。

1
2
3
4
List(1,2,3) <*> List((_:Int) + 1, (_:Int) * 100, (x:Int) => x * x)
// res0: List[Int] = List(2, 3, 4, 100, 200, 300, 1, 4, 9)
(List(1,2,3) |@| List(2,3,4)) {_ + _}
// res1: List[Int] = List(3, 4, 5, 4, 5, 6, 5, 6, 7)

有时候我们不想得到这种笛卡尔积式的结果,只是希望简单的把按顺序对应的Apply一下,这个比较麻烦。

1
2
3
4
5
6
7
8
val xs: List[Int] = List(1, 2, 3)
val fs: List[Int => Int] = List((_:Int) + 1, (_:Int) * 3, (_:Int) + 5)
val zippedLists = streamZipApplicative.ap(Tags.Zip(xs.toStream)) (Tags.Zip(fs.toStream))
val result = Tag.unwrap(zippedLists).toList
// result: List[Int] = List(2, 6, 8)

Useful functions for Applicatives

Haskell里面定义了liftA2,类型为liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c

他把普通的函数提升成应用在Applicative上。

1
2
3
val k = Apply[Option].lift2((_: Int) + (_: Int))
k(3.some, 5.some) // res0: Option[Int] = Some(8)

还有一个sequenceA,把ApplicativeList变成ListApplicative

1
2
3
sequenceA :: (Applicative f) => [f a] => f [a]
sequenceA [] = pure []
sequenceA (x:xs) = (:) <*> x <*> sequenceA xs

scalaz

1
2
3
4
5
6
7
8
9
def sequenceA[F[_]: Applicative, A](list: List[F[A]]): F[List[A]] = list match {
case Nil => (Nil: List[A]).point[F]
case x::xs => (x |@| sequenceA(xs)) {_ :: _}
}
sequenceA(List(1.some, 3.some)) // res0: Option[List[Int]] = Some(List(1, 3))
sequenceA(List(1.some, none, 3.some)) // res1: Option[List[Int]] = None
sequenceA(List(List(1,2,3), List(4,5,6)))

对于,有多个类型参数的,我们只能采用一些黑科技了。

1
2
3
type Function1Int[A] = (Int) => A
sequenceA(List((_:Int) + 1, (_: Int) * 3, (_: Int) * 9): List[Function1Int[Int]])

先把类型弄个别名,然后让它变成只有一个类型参数的类型。




X