函数语法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) 

所以我有几个问题:

  1. 该方法似乎是采取一种types参数( M[B] )的更高types ,但可以通过Validation (有两种types的参数)?
  2. 语法(_: A, _: B)定义了第二种方法期望的函数(A, B) => Pair[A,B]失败情况下Tuple2 / Pair发生了什么? 看不到任何元组!

键入构造函数作为types参数

M是Scalaz的主要皮条客之一MA的一个types参数,它表示皮尔曼值的types构造函数(又名Higher Kinded Type)。 这个types的构造函数用于查找FunctorApply的适当实例,这是对方法<**>隐式需求。

 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构造函数。 您可以将Inttypes应用于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是独立的,所以我们可以通过应用程序将它们结合起来,收集我们遇到的所有失败。 应用软件的弱点已经成为一种力量!