函数语法puzzler在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))
我试图理解<|*|>
方法在做什么,这里是源代码:
def <|*|>[B](b: M[B])(implicit t: Functor[M], a: Apply[M]): M[(A, B)] = <**>(b, (_: A, _: B))
好吧,这相当混乱(!) – 但它引用了<**>
方法,这是声明:
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)
所以我有几个问题:
- 该方法似乎是采取一种types参数(
M[B]
)的更高types ,但可以通过Validation
(有两种types的参数)? - 语法
(_: A, _: B)
定义了第二种方法期望的函数(A, B) => Pair[A,B]
: 失败情况下Tuple2 / Pair发生了什么? 看不到任何元组!
键入构造函数作为types参数
M
是Scalaz的主要皮条客之一MA的一个types参数,它表示皮尔曼值的types构造函数(又名Higher Kinded Type)。 这个types的构造函数用于查找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] = ... }
什么是types构造器?
从Scala语言参考:
我们区分一阶types和types构造函数,它们使用types参数和产出types。 称为值types的一阶types的子集表示(第一类)值的集合。 价值types是具体的或抽象的。 每一个具体的值types都可以表示为一个类的types,也就是types指示符(§3.2.3),它指的是一个类1(§5.3),或者表示types交集的复合types(§3.2.7) (§3.2.7)进一步限制了其成员的types。 抽象值types由types参数(§4.4)和抽象types绑定(§4.3)引入。 types中的圆括号用于分组。 我们假定对象和包也隐含地定义了一个类(与对象或包相同的名称,但用户程序不可访问)。
非值types捕获不是值的标识符的属性(§3.3)。 例如,一个types构造函数(§3.3.3)不直接指定值的types。 但是,将types构造函数应用于正确的types参数时,会生成一个一阶types,这可能是一个值types。 非值types在Scala中间接表示。 例如,方法types是通过写下方法签名来描述的,虽然它产生了相应的函数types(§3.3.1),但是方法签名本身并不是真正的types。 types构造函数是另外一个例子,因为可以写typesSwap [m [_,_],a,b] = m [b,a],但是没有语法直接编写相应的匿名types函数。
List
是一个types构造函数。 您可以将Int
types应用于Valuetypes, List[Int]
,它可以对值进行分类。 其他types的构造函数需要多个参数。
特征scalaz.MA
要求它的第一个types参数必须是一个types构造函数,它需要一个types返回一个值types,语法trait MA[M[_], A] {}
。 types参数定义描述了types构造函数的形状,它被称为Kind。 List
被认为具有那种' * -> *
。
types的部分应用
但是, MA
如何包装Validation[X, Y]
types的值呢? typesValidation
有一种(* *) -> *
,只能作为typesparameter passing给types参数,如M[_, _]
。
对象Scalaz中的这种隐式转换将typesValidation[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) }
然后在PartialApply1Of2中使用types别名的技巧来部分应用types构造函数Validation
,修复错误的types,但不应用成功的types。
PartialApply1Of2[Validation, E]#Apply
最好写成[X] => Validation[E, X]
。 我最近提议给Scala添加这样的语法,可能会在2.9版本中发生。
把它看作是一个相当于这个types的types:
def validation[A, B](a: A, b: B) = ... def partialApply1Of2[A, BC](f: (A, B) => C, a: A): (B => C) = (b: B) => f(a, b)
这使您可以将Validation[String, Int]
与Validation[String, Boolean]
,因为两者共享types构造函数[A] Validation[String, A]
。
应用函子
<**>
要求types构造函数M
必须具有Apply和Functor的关联实例。 这构成了一个应用函数,它像Monad一样,是通过一些效果来构造计算的一种方式。 在这种情况下,效果是子计算可能会失败(当它们这样做时,我们累积失败)。
容器Validation[NonEmptyList[String], A]
可以在这个“效果”中包装一个纯typesA
值。 <**>
运算符有两个有效值和一个纯函数,并将它们与该容器的Applicative Functor实例组合在一起。
以下是Option
应用仿函数的工作原理。 这里的“效应”是失败的可能性。
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)
在这两种情况下,都有一个types为(String, Int) => String
的纯函数,应用于有效的参数。 请注意,结果被包装在相同的效果(或容器,如果你喜欢)作为参数。
您可以在具有关联的Applicative Functor的多个容器中使用相同的模式。 所有Monad都自动应用Funrator,但还有更多,比如ZipStream
。
Option
和[A]Validation[X, A]
都是单子,所以你也可以使用Bind
(aka flatMap):
val result3 = oi flatMap { i => os map { s => s * i } } val result4 = for {i <- oi; s <- os} yield s * i
用“<| ** |>”来debugging
<|**|>
与<**>
非常相似,但是它提供了纯函数,可以简单地从结果中构build一个Tuple2。 (_: A, _ B)
是(a: A, b: B) => Tuple2(a, b)
超越
这是我们的应用程序和validation捆绑的例子。 我用一个稍微不同的语法来使用Applicative Functor, (fa ⊛ fb ⊛ fc ⊛ fd) {(a, b, c, d) => .... }
更新:但是在失败案例中发生了什么?
Tuple2 / Pair在失败情况下发生了什么?
如果任何子计算失败,则提供的function从不运行。 只有在所有的子计算(在本例中,传递给<**>
的两个参数)都成功的情况下才会运行。 如果是这样,它将这些组合成一个Success
。 这个逻辑在哪里? 这定义了[A] Validation[X, A]
的Apply
实例。 我们要求typesX必须有一个可用的Semigroup
,这是将各个types错误组合成相同types的聚合错误的策略。 如果selectString
作为错误types,那么Semigroup[String]
连接string; 如果您selectNonEmptyList[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) } }
Monad或Applicative,我该如何select?
还在读书? ( 是的,Ed )
我已经展示了基于Option
或[A] Validation[E, A]
子计算可以与Apply
或者Bind
结合使用。 你什么时候select一个呢?
当你使用Apply
,计算的结构是固定的。 所有的子计算将被执行; 一个人的结果不能影响其他人。 只有“纯粹的”function才能对发生的事情进行概述。 另一方面,一元计算允许第一个子计算影响后面的计算。
如果我们使用Monadicvalidation结构,则第一次失败将会使整个validation短路,因为没有Success
值将被馈送到随后的validation中。 然而,我们很高兴子validation是独立的,所以我们可以通过应用程序将它们结合起来,收集我们遇到的所有失败。 应用软件的弱点已经成为一种力量!