oxb*_*kes 51 functional-programming scala scala-2.8 scalaz
在观看Nick Partidge关于导出scalaz的演示之后,我开始看这个例子,这真是太棒了:
import scalaz._
import Scalaz._
def even(x: Int) : Validation[NonEmptyList[String], Int]
= if (x % 2 ==0) x.success else "not even: %d".format(x).wrapNel.fail
println( even(3) <|*|> even(5) ) //prints: Failure(NonEmptyList(not even: 3, not even: 5))
Run Code Online (Sandbox Code Playgroud)
我试图了解<|*|>
方法正在做什么,这里是源代码:
def <|*|>[B](b: M[B])(implicit t: Functor[M], a: Apply[M]): M[(A, B)]
= <**>(b, (_: A, _: B))
Run Code Online (Sandbox Code Playgroud)
好的,这是相当令人困惑的(!) - 但它引用了<**>
声明的方法:
def <**>[B, C](b: M[B], z: (A, B) => C)(implicit t: Functor[M], a: Apply[M]): M[C]
= a(t.fmap(value, z.curried), b)
Run Code Online (Sandbox Code Playgroud)
所以我有几个问题:
M[B]
)但是可以通过a Validation
(它有两个类型的参数)?(_: A, _: B)
定义了第二种(A, B) => Pair[A,B]
方法所期望的功能:在故障情况下Tuple2/Pair发生了什么?看不到任何元组!ret*_*nym 64
M
是一个Scalaz的主皮条客MA的类型参数,它代表了拉皮条值的类型构造函数(又名高级金币类型).此类型构造函数用于查找Functor
和的相应实例Apply
,它们是方法的隐式需求<**>
.
trait MA[M[_], A] {
val value: M[A]
def <**>[B, C](b: M[B], z: (A, B) => C)(implicit t: Functor[M], a: Apply[M]): M[C] = ...
}
Run Code Online (Sandbox Code Playgroud)
来自Scala语言参考:
我们区分了一阶类型和类型构造函数,它们采用类型参数和产量类型.称为值类型的一阶类型的子集表示(第一类)值的集合.值类型是具体的或抽象的.每个具体的值类型都可以表示为类类型,即类型指示符(第3.2.3节)引用类1(第5.3节),或者表示类型交集的复合类型(第3.2.7节),可能通过细化(§3.2.7)进一步限制其成员的类型.抽象值类型由类型参数(§4.4)和抽象类型绑定(§4.3)引入.类型中的括号用于分组.我们假设对象和包也隐式定义了一个类(与对象或包同名,但用户程序无法访问).
非值类型捕获非值的标识符的属性(§3.3).例如,类型构造函数(第3.3.3节)不直接指定值的类型.但是,当类型构造函数应用于正确的类型参数时,它会产生一阶类型,可以是值类型.非值类型在Scala中间接表示.例如,通过写下方法签名来描述方法类型,方法签名本身不是真实类型,尽管它产生相应的函数类型(§3.3.1).类型构造函数是另一个例子,因为可以写类型Swap [m [_,_],a,b] = m [b,a],但是没有语法直接编写相应的匿名类型函数.
List
是一个类型构造函数.您可以应用类型Int
来获取值类型List[Int]
,它可以对值进行分类.其他类型构造函数需要多个参数.
该特征scalaz.MA
要求它的第一个类型参数必须是一个类型构造函数,它使用单一类型返回值类型,并带有语法trait MA[M[_], A] {}
.类型参数定义描述了类型构造函数的形状,它被称为Kind.List
据说有那种' * -> *
.
但是如何MA
包装类型的值Validation[X, Y]
?该类型Validation
有一种(* *) -> *
,只能作为类型参数传递给声明为的类型参数M[_, _]
.
对象Scalaz中的隐式转换将type的值转换Validation[X, Y]
为MA
:
object Scalaz {
implicit def ValidationMA[A, E](v: Validation[E, A]): MA[PartialApply1Of2[Validation, E]#Apply, A] = ma[PartialApply1Of2[Validation, E]#Apply, A](v)
}
Run Code Online (Sandbox Code Playgroud)
而在PartialApply1Of2中又使用类型别名的技巧来部分应用类型构造函数Validation
,修复错误类型,但保留未成功的类型.
PartialApply1Of2[Validation, E]#Apply
会写得更好[X] => Validation[E, X]
.我最近建议为Scala添加这样的语法,它可能发生在2.9中.
可以将其视为与此类似的类型级别:
def validation[A, B](a: A, b: B) = ...
def partialApply1Of2[A, B C](f: (A, B) => C, a: A): (B => C) = (b: B) => f(a, b)
Run Code Online (Sandbox Code Playgroud)
这允许你Validation[String, Int]
与a 结合Validation[String, Boolean]
,因为它们共享类型构造函数[A] Validation[String, A]
.
<**>
要求类型构造函数M
必须具有Apply和Functor的关联实例.这构成了一个Applicative Functor,它像Monad一样,是一种通过某种效果构建计算的方法.在这种情况下,效果是子计算可能失败(当它们发生时,我们会累积失败).
容器Validation[NonEmptyList[String], A]
可以A
在此"效果"中包含纯值类型.该<**>
操作需要两个effectful值,和一个纯函数,并与适用函子实例该容器将它们组合.
以下是它适用于Option
applicative functor的方法.这里的"效果"是失败的可能性.
val os: Option[String] = Some("a")
val oi: Option[Int] = Some(2)
val result1 = (os <**> oi) { (s: String, i: Int) => s * i }
assert(result1 == Some("aa"))
val result2 = (os <**> (None: Option[Int])) { (s: String, i: Int) => s * i }
assert(result2 == None)
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,都有一个类型的纯函数(String, Int) => String
,应用于有效的参数.请注意,结果包含在相同的效果(或容器,如果您愿意),作为参数.
您可以在具有关联的Applicative Functor的众多容器中使用相同的模式.所有Monads都是自动应用的Functors,但还有更多,如ZipStream
.
Option
并且[A]Validation[X, A]
都是Monads,所以你也可以使用Bind
(又名flatMap):
val result3 = oi flatMap { i => os map { s => s * i } }
val result4 = for {i <- oi; s <- os} yield s * i
Run Code Online (Sandbox Code Playgroud)
<|**|>
是非常相似的<**>
,但它为您提供了从结果中简单地构建Tuple2的纯函数.(_: A, _ B)
是一个简写(a: A, b: B) => Tuple2(a, b)
这是我们的应用和验证的捆绑示例.我使用稍微不同的语法来使用Applicative Functor,(fa ? fb ? fc ? fd) {(a, b, c, d) => .... }
失败案例中Tuple2/Pair发生了什么?
如果任何子计算失败,则永远不会运行提供的函数.仅当所有子计算(在这种情况下,传递给的两个参数<**>
)成功时才运行它.如果是这样,它将这些组合成一个Success
.这个逻辑在哪里?这定义了Apply
实例[A] Validation[X, A]
.我们要求类型X必须具有Semigroup
可用性,这是将各个类型的各个错误组合X
成相同类型的聚合错误的策略.如果选择String
作为错误类型,则Semigroup[String]
连接字符串; 如果您选择NonEmptyList[String]
,每个步骤中NonEmptyList
的错误将连接成更长的错误.当Failures
使用?
运算符组合两个时,这种连接发生在下面(例如,使用运算符扩展)Scalaz.IdentityTo(e1).?(e2)(Semigroup.NonEmptyListSemigroup(Semigroup.StringSemigroup))
.
implicit def ValidationApply[X: Semigroup]: Apply[PartialApply1Of2[Validation, X]#Apply] = new Apply[PartialApply1Of2[Validation, X]#Apply] {
def apply[A, B](f: Validation[X, A => B], a: Validation[X, A]) = (f, a) match {
case (Success(f), Success(a)) => success(f(a))
case (Success(_), Failure(e)) => failure(e)
case (Failure(e), Success(_)) => failure(e)
case (Failure(e1), Failure(e2)) => failure(e1 ? e2)
}
}
Run Code Online (Sandbox Code Playgroud)
还在看?(是的.Ed)
我已经证明了基于Option
或[A] Validation[E, A]
可以与其中任何一个Apply
或与之结合的子计算Bind
.你何时会选择一个而不是另一个?
使用时Apply
,计算结构是固定的.将执行所有子计算; 一个人的结果不能影响其他人.只有"纯"函数才能概述发生的情况.另一方面,Monadic计算允许第一个子计算影响后面的计算.
如果我们使用Monadic验证结构,则第一次失败会使整个验证短路,因为没有任何Success
值可以用于后续验证.但是,我们很高兴子验证是独立的,因此我们可以通过Applicative将它们组合在一起,并收集我们遇到的所有失败.Applicative Functors的弱点已成为一种力量!