[关闭]
@MiloXia 2015-11-01T13:00:02.000000Z 字数 14587 阅读 1601

Scalaz doc

scala 函数式编程


真:http://docs.typelevel.org/api/scalaz/stable/7.0.4/doc/

Equal, Order, Show

Equal

提供类型安全的判等
源码:core/.../scalaz/Equal.scala
要求:实现equal(a1,a2) / equalBy / Equal.equal
注入方法:scalaz/syntax/EqualSyntax.scala

  1. case class Person(name: String, age: Int)
  2. implicit val personEqual: Equal[Person] =
  3. Equal.equal{(a,b) => a.name == b.name && a.age == b.age}
  4. //或
  5. implicit val personEqual = new Equal[Person] {
  6. def equal(a1: Person, a2: Person): Boolean =
  7. a1.name == a2.name && a1.age == a2.age
  8. }
  9. Person("Jone",23) === Person("Jone",23) //true
  10. //其它方法: =/= 、assert_=== (会报异常的)
  1. //逆变函数 通过G=>F的函数(同构函数??)(Equal[F]已知)得到Equal[G],同态?? 委托??
  2. def contramap[G](f: G => F): Equal[G] = new Equal[G] {
  3. def equal(a1: G, a2: G) = self.equal(f(a1), f(a2))
  4. }
  5. //Equal伴生对象提供
  6. def equalBy[A, B: Equal](f: A => B): Equal[A] = Equal[B] contramap f
  7. case class MoneyCents(cents: Int)
  8. def moneyToInt(m: MoneyCents): Int = m.cents * 100
  9. implicit val moneyEqual: Equal[MoneyCents] = Equal.equalBy(moneyToInt)
  10. MoneyCents(120) === MoneyCents(120) //true

Order

类型安全的比较大小?
要求:实现order(a1,a2) / Order.order / orderBy
注入方法:scalaz/syntax/OrderSyntax.scala

  1. case class Person(name: String, age: Int)
  2. implicit val personAgeOrder = new Order[Person] {
  3. def order(a1: Person, a2: Person): Ordering =
  4. if (a1.age < a2.age) Ordering.LT else if (a1.age > a2.age) Ordering.GT else Ordering.EQ
  5. }
  6. //多用lt gt
  7. Person("John",23) ?|? Person("Joe",24) //LT
  8. Person("John",23) lt Person("Joe",24) //true
  9. Person("John",23) gt Person("Joe",24) //false
  10. //或Order.order
  11. case class Meat(cat: String, weight: Int)
  12. implicit val meatWeightOrder: Order[Meat] = Order.order(_.weight ?|? _.weight)
  13. Meat("Pork",13) lt Meat("Pork",14) //true
  14. Meat("Beef",13) gt Meat("Pork",14) //false
  15. //或orderBy 逆变构建函数
  16. case class Money(amount: Int)
  17. val moneyToInt: Money => Int = money => money.amount
  18. implicit val moneyOrder: Order[Money] = Order.orderBy(moneyToInt)

Show

就是toString, 不是所有类型的toString都有意义,但实现show的都是有意义的
要求:
注入方法:scalaz/Syntax/ShowSyntax.scala

  1. case class Person(name: String, age: Int)
  2. implicit val personShow: Show[Person] = Show.show {p => p.name + "," + p.age + " years old" }
  3. Person("Harry",24).shows // Harry,24 years old
  4. Person("Harry",24).println // Harry,24 years old 真打印不是返回String
  5. 3.show // scalaz.Cord = 3 //Cord表示可能很长的字符串
  6. 3.shows // String = 3

Enum

要求:实现succ,pred 以及order(a1,a2) (继承自Order)
注入方法:class EnumOps[F]

  1. 'a' |-> 'e' // List(a, b, c, d, e)
  2. 'a'.succ // b
  3. 'a' -+- 2 // c
  4. 'd' --- 2 // b

Bounded 提供和定义边界

  1. implicitly[Enum[Char]].min //res43: Option[Char] = Some(?)
  2. implicitly[Enum[Char]].max //res44: Option[Char] = Some( )
  3. implicitly[Enum[Double]].max //res45: Option[Double] = Some(1.7976931348623157E308)
  4. implicitly[Enum[Int]].min //res46: Option[Int] = Some(-2147483648)
  5. implicitly[Enum[(Boolean, Int, Char)]].max
  6. <console>:14: error: could not find implicit value for parameter e: scalaz.Enum[(Boolean, Int, Char)]
  7. implicitly[Enum[(Boolean, Int, Char)]].max

Functor

定义:scalaz/Functor.scala
要求:实现map
注入方法:scalaz/syntax/FunctorSyntax.scala

F.map

  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. }
  5. val F = Functor[Item3]
  6. //前方高能。。。
  7. F.map(Item3("Morning","Noon","Night"))(_.length) //> res0: scalaz.functor.Item3[Int] = Item3(7,4,5)
  8. F.apply(Item3("Morning","Noon","Night"))(_.length)//> res1: scalaz.functor.Item3[Int] = Item3(7,4,5)
  9. //这种不错
  10. F(Item3("Morning","Noon","Night"))(_.length) //> res2: scalaz.functor.Item3[Int] = Item3(7,4,5)
  11. //map (A => B) => (F[A] => F[B]),就是把(A => B)升格(lift)成(F[A] => F[B])
  12. F.lift((s: String) => s.length)(Item3("Morning","Noon","Night")) //> res3: scalaz.functor.Item3[Int] = Item3(7,4,5)

Functor law

1、map(fa)(x => x) === fa //id
2、map(map(fa)(f1))(f2) === map(fa)(f2 compose f1) //结合律

  1. //定义产生器
  2. implicit def item3Arbi[A](implicit a: Arbitrary[A]): Arbitrary[Item3[A]] = Arbitrary {
  3. def genItem3: Gen[Item3[A]] = for {
  4. b <- Arbitrary.arbitrary[A]
  5. c <- Arbitrary.arbitrary[A]
  6. d <- Arbitrary.arbitrary[A]
  7. } yield Item3(b,c,d)
  8. genItem3
  9. }
  10. functor.laws[Item3].check

Function1 Functor == compose

前方高能

  1. (((_: Int) + 1) map((k: Int) => k * 3))(2) // 9
  2. (((_: Int) + 1) map((_: Int) * 3))(2) // 9
  3. (((_: Int) + 1) andThen ((_: Int) * 3))(2) //9
  4. (((_: Int) * 3) compose ((_: Int) + 1))(2) //9

Functor的compose

  1. val f = Functor[List] compose Functor[Item3]
  2. val item3 = Item3("Morning","Noon","Night")
  3. f.map(List(item3,item3))(_.length) // List(Item3(7,4,5), Item3(7,4,5))
  4. //直接对内层Functor进行map
  5. //反过来
  6. val f1 = Functor[Item3] compose Functor[List]
  7. f1.map(Item3(List("1"),List("22"),List("333")))(_.length) // Item3(List(1),List(2),List(3))

lift

  1. val fmap = Functor[List].lift {(_: Int) * 2} // fmap: List[Int] => List[Int] = <function1> lift成List[Int] => List[Int]
  2. fmap(List(3)) // List(6)

other注入方法--没啥卵用

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

Scalaz现成的Functor实例

能想到的都有

  1. Functor[List].map(List(1,2,3))(_ + 3) // List(4, 5, 6)
  2. Functor[Option].map(Some(3))(_ + 3) // Some(6)
  3. Functor[java.util.concurrent.Callable] // scalaz.Functor[java.util.concurrent.Callable]
  4. Functor[Stream] // scalaz.Functor[Stream]
  5. Functor[Vector] // scalaz.Functor[Vector]

多个类型变量的Functor--Either[E,A]

用type lambda国定E, 反正留最后的A (一般规律)

  1. Functor[({type l[x] = Either[String,x]})#l].map(Right(3))(_ + 3) // Right(6)

FunctionN的Functor

针对返回类型--这是语义

  1. //1参
  2. Functor[({type l[x] = String => x})#l].map((s: String) => s + "!")(_.length)("Hello") // 6
  3. //2参
  4. Functor[({type l[x] = (String,Int) => x})#l].map((s: String, i: Int) => s.length + i)(_ * 10)("Hello",5) // 100
  5. //3参
  6. 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) // HELLO3TRUE

tuple类型的Functor

针对最后一个元素类型

  1. Functor[({type l[x] = (String,x)})#l].map(("a",1))(_ + 2) // (a,3)
  2. Functor[({type l[x] = (String,Int,x)})#l].map(("a",1,"b"))(_.toUpperCase) // (a,1,B)
  3. Functor[({type l[x] = (String,Int,Boolean,x)})#l].map(("a",1,true,Item3("a","b","c")))(i => i.map(_.toUpperCase)) // (a,1,true,Item3(A,B,C))

Applicative

定义: scalaz/Applicative.scala & scalaz/Apply.scala
要求:实现 point,ap,map
一旦实现了Applicative实例就能同时获取了Functor实例
注入方法:scalaz/Apply.scala & scalaz/syntax/ApplicativeSyntax.scala

  1. trait Configure[+A] { def get: A }
  2. object Configure {
  3. def apply[A](data: => A) = new Configure[A] { def get = data }
  4. //实现Functor
  5. implicit val configFunctor = new Functor[Configure] {
  6. def map[A,B](ca: Configure[A])(f: A => B): Configure[B] = Configure(f(ca.get))
  7. }
  8. //实现Applicative
  9. //def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]
  10. //: => 的好处可传任何的对象/函数/方法,可实现lazy
  11. //call-by-name 一般是call-by-value
  12. implicit val configApplicative = new Applicative[Configure] {
  13. def point[A](a: => A) = Configure(a)
  14. def ap[A,B](ca: => Configure[A])(cfab: => Configure[A => B]): Configure[B] =
  15. cfab map {fab => fab(ca.get)}
  16. }
  17. }

point 构建Applicative

又可叫pure

  1. //一般方式:
  2. val fapply = List(1, 2, 3, 4) map {(_: Int) * (_:Int)}.curried // List[Int => Int] = List(<function1>, <function1>, <function1>, <function1>)
  3. fapply map {_(9)} // List(9, 18, 27, 36)
  4. //point/pure
  5. 1.point[List] // List(1)
  6. 1.point[Option] // Some(1)
  7. 1.point[Option] map {_ + 2} // Some(3)
  8. 1.point[List] map {_ + 2} // List(3)
  9. "abc".point[Configure] // Configure[String]
  10. 12.point[Configure] // Configure[String]

Apply

通过Apply的ap2,ap3,ap4 ...款式的函数我们可以把 F[A],F[B],F[C],F[D]...多个值连接起来

apN

  1. Apply[Configure].ap2(Configure(1),Configure(2))(((_: Int) + (_: Int)).point[Configure]) //Configure(3)

<*> -- 推荐

  1. (Configure(1) <*> {Configure(2) <*> {Configure(3) <*> {(((_:Int)+(_:Int)+(_:Int)).curried).point[Configure]}}}).get // 6

applyN -- 推荐

apply2,apply3..只用提供f:(A,B) => C 基本函数 (无需point)

  1. (Apply[Configure].apply2(Configure(1),Configure(2))(((_: Int) + (_: Int)))).get
  2. (^(Configure(1),Configure(2))((_:Int)+(_:Int))).get
  3. (^^(Configure(1),Configure(2),Configure(3))((_:Int)+(_:Int)+(_:Int))).get

|@|

通过ApplicativeBuilder typeclass实现的注入方法

  1. ((Configure(1) |@| Configure(2) |@| Configure(3))((_:Int)+(_:Int)+(_:Int))).get // 6

exp

  1. def configName(name: String): Configure[String] = Configure(name)
  2. def configID(userid: String): Configure[String] = Configure(userid)
  3. def configPwd(pwd: String): Configure[String] = Configure(pwd)
  4. case class WebLogForm(name:String, id: String, pwd: String)
  5. def logOnWeb(name: String, userid: String, pwd: String) =
  6. ^^(configName(name),configID(userid), configPwd(pwd))(WebLogForm(_,_,_))
  7. def logOnWeb1(name: String, userid: String, pwd: String) =
  8. (configName(name) |@| configID(userid) |@| configPwd(pwd))(WebLogForm(_,_,_))

用Applicative施用configName,configID,configPwd时,这三个函数之间没有依赖关系。特别适合并行运算或fail-fast,因为无论如何这三个函数都一定会运行。这种Applicative的函数施用体现了它在并行运算中的优势。

*> 和 <* -- 没卵用

只返回右手边或左手边

  1. 1.some <* 2.some // Some(1)
  2. none <* 2.some // None
  3. 1.some *> 2.some // Some(2)
  4. none *> 2.some // None

Applicative Style

(F[A] |@| F[B] |@| F[C])((A,B,C) => D)
|@|操作并不是一种操作函数而是一种层级式持续函数施用模式:t (<*>)
|@|定义scalaz/ApplicativeBuilder.scala

tupleN

(F[A], F[B]) 合并成 F[(A, B)] :scalaz/Apply.scala
tuple2 == apply2(fa, fb)((_, _))
(List(F[A], F[A]) => F[List[A]] 是sequeue)

  1. Apply[Configure].tuple2(Configure("abc"),Configure(123)) // Configure[(String, Int)]
  2. Apply[Configure].tuple3(Configure("abc"),Configure(123),Configure(true)) // Configure[(String, Int, Boolean)]

liftN -- (A, B) => C -> (F[A], F[B]) => F[C]

比functor的lift 要多参数
def lift2[A, B, C](f: (A, B) => C): (F[A], F[B]) => F[C] = apply2(_, _)(f)

  1. val of2 = Apply[Option].lift2((_: Int) + (_: Int))
  2. of2(Some(1),Some(2)) // Some(3)
  3. val of3 = Apply[List].lift3((s1: String, s2: String, s3: String) => s1 + " "+s2+" "+s3)
  4. of3(List("How"),List("are"),List("you?")) // List(How are you?)
  5. //前方高能
  6. import java.sql.DriverManager
  7. val sqlConnect = Apply[Configure] lift3 java.sql.DriverManager.getConnection
  8. sqlConnect(Configure("Source"),Configure("User"),Configure("Password")) // Configure[java.sql.Connection]
  9. //使我们继续在FP模式中工作

Zip Lists -- 没啥卵用

希望[(+3),(2)] <> [1,2] 得到[4,4], 对list中的元素施用不同的函数

  1. streamZipApplicative.ap(Tags.Zip(Stream(1, 2))) (Tags.Zip(Stream({(_: Int) + 3}, {(_: Int) * 2}))).toList // List(4, 4)

sequenceA

(Applicative f) => [f a] -> f [a] 把Applicative的List变成List的Applicative, 和基于Monad的sequence是一样的

  1. def sequenceA[F[_]: Applicative, A](list: List[F[A]]): F[List[A]] = list match {
  2. case Nil => (Nil: List[A]).point[F]
  3. case x :: xs => (x |@| sequenceA(xs)) {_ :: _}
  4. }
  5. sequenceA(List(1.some, 2.some)) // Some(List(1, 2))
  6. sequenceA(List(3.some, none, 1.some)) // None
  7. sequenceA(List(List(1, 2, 3), List(4, 5, 6))) // List(List(1, 4), List(1, 5), List(1, 6), List(2, 4), List(2, 5), List(2, 6), List(3, 4), List(3, 5), List(3, 6))
  8. type Function1Int[A] = ({type l[A]=Function1[Int, A]})#l[A]
  9. //等于type Function1Int[A] = Function1[Int, A]
  10. val f: Int => List[Int] = sequenceA(List((_: Int) + 3, (_: Int) + 2, (_: Int) + 1): List[Function1Int[Int]])
  11. //Int => List[Int] = <function1>
  12. f(3) // List(6, 5, 4)

Combining applicative functors

  1. //product 产生元组
  2. Applicative[List].product[Option].point(1) // (List(1),Some(1))
  3. //对元组操作
  4. ((List(1), 1.some) |@| (List(1), 1.some)) {_ |+| _} // (List(1, 1),Some(2))
  5. ((List(1), 1.success[String]) |@| (List(1), "boom".failure[Int])) {_ |+| _} // (List(1, 1),Failure(boom))
  6. //生成嵌套
  7. Applicative[List].compose[Option].point(1) // List(Some(1))

Kinds

Scala木有:k,这是一个手写版

  1. def kind[A: scala.reflect.runtime.universe.TypeTag]: String = {
  2. import scala.reflect.runtime.universe._
  3. def typeKind(sig: Type): String = sig match {
  4. case PolyType(params, resultType) =>
  5. (params map { p =>
  6. typeKind(p.typeSignature) match {
  7. case "*" => "*"
  8. case s => "(" + s + ")"
  9. }
  10. }).mkString(" -> ") + " -> *"
  11. case _ => "*"
  12. }
  13. def typeSig(tpe: Type): Type = tpe match {
  14. case SingleType(pre, sym) => sym.companionSymbol.typeSignature
  15. case ExistentialType(q, TypeRef(pre, sym, args)) => sym.typeSignature
  16. case TypeRef(pre, sym, args) => sym.typeSignature
  17. }
  18. val sig = typeSig(typeOf[A])
  19. val s = typeKind(sig)
  20. sig.typeSymbol.name + "'s kind is " + s + ". " + (s match {
  21. case "*" =>
  22. "This is a proper type."
  23. case x if !(x contains "(") =>
  24. "This is a type constructor: a 1st-order-kinded type."
  25. case x =>
  26. "This is a type constructor that takes type constructor(s): a higher-kinded type."
  27. })
  28. }

使用:

  1. scala> kind[Int]
  2. res0: String = Int's kind is *.
  3. This is a proper type.
  4. scala> kind[Option.type]
  5. res1: String = Option's kind is * -> *.
  6. This is a type constructor: a 1st-order-kinded type.
  7. scala> kind[Either.type]
  8. res2: String = Either's kind is * -> * -> *.
  9. This is a type constructor: a 1st-order-kinded type.
  10. scala> kind[Equal.type]
  11. res3: String = Equal's kind is * -> *.
  12. This is a type constructor: a 1st-order-kinded type.
  13. scala> kind[Functor.type]
  14. res4: String = Functor's kind is (* -> *) -> *.
  15. This is a type constructor that takes type constructor(s): a higher-kinded type.

Tagged -- newtype

  1. //如果要抽象出kg,用case class 太麻烦,每次要解值
  2. case class KiloGram(value: Double)
  3. //newtype KiloGram = KiloGram Double
  4. sealed trait KiloGram
  5. def KiloGram[A](a: A): A @@ KiloGram = Tag[A, KiloGram](a) // scalaz.@@[Double,KiloGram] = 20.0
  6. val mass = KiloGram(20.0)
  7. 2 * mass // Double = 40.0

Monoid and Foldable

Monoid

定义:scalaz/Monoid.scala & scalaz/Semigroup.scala
要求:实现zero & append
注入方法:|+|

  1. 0 |+| 30 // 50
  2. 20.some |+| 30.some // Some(50)
  3. List(1,2,3) |+| List(4,5,6) // List(1, 2, 3, 4, 5, 6)
  4. //Int的乘法群
  5. Tags.Multiplication(3) |+| Monoid[Int @@ Tags.Multiplication].zero // scalaz.@@[Int,scalaz.Tags.Multiplication] = 3
  6. //boolean的monoid是有方向的(&& 和 ||)
  7. Tags.Conjunction(true) |+| Tags.Conjunction(false) // scalaz.@@[Boolean,scalaz.Tags.Conjunction] = false
  8. Tags.Disjunction(true) |+| Tags.Disjunction(false) // scalaz.@@[Boolean,scalaz.Tags.Disjunction] = true
  9. //看来boolean的monoid的幺元是true或false(不太严谨,不过也对)
  10. Monoid[Boolean @@ Tags.Conjunction].zero // scalaz.@@[Boolean,scalaz.Tags.Conjunction] = true
  11. Monoid[Boolean @@ Tags.Disjunction].zero // scalaz.@@[Boolean,scalaz.Tags.Disjunction] = false

Ordering Monoid

scalaz/Ordering.scala
两个Ordering类型值f1,f2的append操作结果:假如f1是EQ就是f2,否则是f1

  1. (Ordering.EQ: Ordering) |+| (Ordering.GT: Ordering) // scalaz.Ordering = GT
  2. (Ordering.EQ: Ordering) |+| (Ordering.LT: Ordering) // scalaz.Ordering = LT
  3. (Ordering.GT: Ordering) |+| (Ordering.EQ: Ordering) // scalaz.Ordering = GT
  4. (Ordering.LT: Ordering) |+| (Ordering.EQ: Ordering) // scalaz.Ordering = LT
  5. (Ordering.LT: Ordering) |+| (Ordering.GT: Ordering) // scalaz.Ordering = LT
  6. (Ordering.GT: Ordering) |+| (Ordering.LT: Ordering) // scalaz.Ordering = GT

用以上的特性来比较两个String的长度:如果长度相等则再比较两个String的字符顺序

  1. def strlenCompare(lhs: String, rhs: String): Ordering = (lhs.length ?|? rhs.length) |+| (lhs ?|? rhs)
  2. strlenCompare("abc","aabc") // LT
  3. strlenCompare("abd","abc") // GT

可以用来计算任何有序的(有规则的)比较(Ordering就是用来比较的),再此之前你可以实现自己的Order typeclass

Foldable

定义: scalaz/Foldable.scala
要求: Monoid[B]
注入方法:scalaz/syntax/FoldableSyntax.scala
scalaz为大多数标准库中的集合类型提供了Foldable实例

foldMap

  1. List(1,2,3) foldMap {x => x} // 6
  2. List(1,2,3) foldMap {x => (x + 3).toString} // "456"

Monoid组合

  1. def productMonoid[A,B](ma: Monoid[A], mb: Monoid[B]): Monoid[(A,B)] =
  2. new Monoid[(A,B)] {
  3. def zero = (ma.zero, mb.zero)
  4. def append(x: (A,B), y: => (A,B)): (A,B) =
  5. (ma.append(x._1, y._1), mb.append(x._2, y._2))
  6. }
  7. val pm = productMonoid(Monoid[Int],Monoid[List[Int]])
  8. //我们可以在游览一个List[Int]时同时统计长度(list length)及乘积(product)
  9. val intMultMonoid = new Monoid[Int] {
  10. def zero = 1
  11. def append(a1: Int, a2: => Int): Int = a1 * a2
  12. }
  13. val pm = productMonoid(Monoid[Int @@ Tags.Multiplication],Monoid[Int])
  14. List(1,2,3,4,6).foldMap(i => (i, 1))(productMonoid(intMultMonoid,Monoid[Int])) // (144,5)
  15. //合并多层map的Monoid
  16. def mapMergeMonoid[K,V](V: Monoid[V]): Monoid[Map[K, V]] =
  17. new Monoid[Map[K, V]] {
  18. def zero = Map[K,V]()
  19. def append(a: Map[K, V], b: => Map[K, V]) =
  20. (a.keySet ++ b.keySet).foldLeft(zero) { (acc,k) =>
  21. acc.updated(k, V.append(a.getOrElse(k, V.zero),
  22. b.getOrElse(k, V.zero)))
  23. }
  24. }
  25. val m1 = Map("o1" -> Map("i1" -> 1, "i2" -> 2))
  26. val m2 = Map("o1" -> Map("i2" -> 3))
  27. val m3 = M.append(m1, m2) //Map(o1 -> Map(i1 -> 1, i2 -> 5))

可为如任意对象的任意计算抽象出Monoid(符合 Monoid law)

还可以用这个Monoid来统计一段字串内字符发生的频率

  1. def frequencyMap[A](as: List[A]): Map[A, Int] =
  2. as.foldMap((a: A) => Map(a -> 1))(mapMergeMonoid[A, Int](Monoid[Int]))
  3. frequencyMap("the brown quik fox is running quikly".toList)

Monoid必须在可折叠数据结构(Foldable)内才能正真发挥作用

Monoidal applicatives

  1. Monoid[Int].applicative.ap2(1, 1)(0) // 2
  2. Monoid[List[Int]].applicative.ap2(List(1), List(1))(Nil) // List(1, 1)

Laws

http://www.eed3si9n.com/learning-scalaz/7.0/Functor+Laws.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注