函数语法puzzler在scalaz中 [英] Function syntax puzzler in scalaz
问题描述
继续观看 Nick Partidge的演示文稿,其中介绍了 scalaz ,我得看看这个例子,这真是太棒了:
import scalaz._
import Scalaz._
def even(x:Int):Validation [NonEmptyList [String],Int]
= if(x%2 == 0)x。 (x).wrapNel.fail
println(even(3)< | * |> even(5))//打印:失败( NonEmptyList(甚至不是3,甚至不是5))
我试图理解< | * |>
方法在做,这里是源代码:
def <| * |> [B](b:M [B])(隐式t:Functor [M],a:Apply [M]):M [(A,B)]
= **(b,(_:A,_:B))
好的,这很令人困惑(!) - 但它引用了< **>
方法,它是这样声明的:
$ $ p $
def ** [B,C](b:M [B],z:(A,B)=> C)(隐式t:Functor [M],a:Apply [M]):M [C]
= a(t.fmap(value,z.curried),b)
所以我有几个问题:
- 为什么这个方法似乎需要一个类型参数(
M [B]
)的高级类型,但可以传递一个验证
(它有两个类型参数)? - 语法
(_:A,_:B) code>定义函数
(A,B)=>第二种方法所期望的对[A,B]
:失败情况下Tuple2 / Pair发生了什么? h3> - How come the method appears to take a higher-kinded type of one type parameter (
M[B]
) but can get passed aValidation
(which has two type paremeters)? - The syntax
(_: A, _: B)
defines the function(A, B) => Pair[A,B]
which the 2nd method expects: what is happening to the Tuple2/Pair in the failure case? There's no tuple in sight!
M
是斯卡拉斯主要皮条客之一的类型参数, MA ,表示pimped值的类型构造器(又名Higher Kinded Type)。这种类型的构造函数用于查找适用于 Functor
和 Apply
的实例,这是对方法< code $< **> 。
特质MA [M [_] ,A [A] {
val值:M [A]
def ** [B,C](b:M [B],z:(A,B)=> C )(隐式t:Functor [M],a:Apply [M]):M [C] = ...
}
什么是类型构造函数?
从Scala语言参考:
< blockquote>
我们区分一阶
类型和类型构造函数,其中
使用类型参数和yield类型。
称为
值类型的一阶类型子集表示
(first-class)值的集合。值类型是具体或抽象的
。每个
具体值类型可以表示
作为类类型,也就是类型
指示符(§3.2.3),它指向
class1(§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
要求它的第一个类型参数必须是使用语法特征MA [M [_],A] {}
来返回值类型的类型构造函数。类型参数定义描述了类型构造函数的形状,它被称为它的Kind。 List 据说有'
* - > *
。
类型的部分应用程序
但是 MA
包装类型验证值[X,Y]
的值?类型验证
有一种(* *) - > *
,并且只能作为类型参数传递给类型参数,如 M [_,_]
。
这个隐式转换在将类型为 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)
}
然后在 PartialApply1Of2 部分应用类型构造函数 Validation
,修复类型的错误,但留下成功的类型不适用。
PartialApply1Of2 [Validation,E] #Apply
would最好写成 [X] =>验证[E,X]
。我最近建议为Scala添加这样一种语法,它可能发生在2.9。
将此视为相当于此的类型级别:
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 ]
与一个验证[字符串,布尔]
,因为两者共享类型构造函数 [A] Validation [String, A]
。
Applicative Functors
< **>
要求类型构造函数 M
必须包含应用和 Functor 。这构成了一个Applicative Functor,它与Monad一样,是通过某种效果构造计算的一种方式。在这种情况下,结果是子计算可能会失败(当它们执行时,我们累积失败)。
容器 Validation [ NonEmptyList [String],A]
可以在此'效果'中包装一个纯类型的值 A
。 < **>
运算符有两个有效值和一个纯函数,并将它们与该容器的Applicative Functor实例组合。
以下是它适用于 Option
应用仿函数的方法。这里的'效果'是失败的可能性。
val os:Option [String] =一些(a)
val oi:Option [Int] = Some(2)
val result1 =(os <**> oi){(s:String,i:Int)=> (*)((*)){(*){(*)} { s:String,i:Int)=> s * i}
assert(result2 == None)
在这两种情况下,类型为code>(String,Int)=>的纯函数。字符串,应用于有效的参数。请注意,结果会以相同的效果(或容器,如果您喜欢)包装为参数。
您可以在多个容器中使用相同的模式,有一个相关的Applicative Functor。所有Monad都自动应用了Funative,但还有更多,比如 ZipStream
。
选项
和 [A]验证[X,A]
都是Monad,因此您也可以使用 Bind
(aka flatMap):
val result3 = oi flatMap {i => os map {s => s * i}}
val result4 = for {i < - oi; s< - os} yield s * i
用< | ** |> `
< | ** |>
类似于< * *>
,但它为您提供了纯粹的功能,只需从结果中构建一个Tuple2即可。 (_:A,_ B)
是(a:A,b:B)=>的简写。 Tuple2(a,b)
超越
以下是我们的捆绑示例适用范围和验证。我使用稍微不同的语法来使用Applicative Functor,(fa⊛fb⊛fc⊛fd){(a,b,c,d)=> ....}
更新:但是在失败案例中发生了什么?
< blockquote>
发生故障时Tuple2 / Pair发生了什么?
如果任何子计算失败,则提供的函数永远不会运行。只有在所有子计算(在本例中,传递给< **>
的两个参数)都成功的情况下才会运行。如果是这样,它将这些结合到 Success
中。这个逻辑在哪里?这为 [A]验证[X,A]
定义了应用
实例。我们要求类型X必须有 Semigroup
可用,这是组合单个错误的策略,每种类型 X
,转换为相同类型的聚合错误。如果选择 String
作为错误类型,则 Semigroup [String]
将字符串连接起来;如果选择 NonEmptyList [String]
,则将每个步骤的错误连接到更长的 NonEmptyList
错误。当两个 Failures
合并在一起时,使用⊹
运算符(其含义扩展为例如, Scalaz.IdentityTo(e1)。(e2)(Semigroup.NonEmptyListSemigroup(Semigroup.StringSemigroup))
。
隐式def ValidationApply [X:Semigroup]:Apply [PartialApply1Of2 [Validation,X] #Apply] = new Apply [PartialApply1Of2 [Validation,X] #Apply] {
def apply [ A,B](f:验证[X,A => B],a:验证[X,A])=(f,a)匹配{
案例(成功(f) )成功(f(a))
案例(成功(_),失败(e))=>失败(e)
案例(失败(e),成功(_)) (e1),失败(e2))=>失败(e1→e2)
}
}
$ c
Monad或Applicative,我应该如何选择?
仍在阅读?( Yes。Ed )
我已经展示了基于
Option
的子计算或[A ]验证[E,A]
可以与应用
或与绑定
组合。你什么时候选择一个?
当你使用
Apply
时,计算结构是固定的。所有的子计算都将被执行。一个人的结果不能影响其他人。只有纯粹功能可以概括发生的事情。另一方面,一次计算允许第一个子计算影响后面的计算。
如果我们使用Monadic验证结构,第一次失败会短路整个验证,因为不会有
Success
值供给后续验证。但是,我们很高兴子验证是独立的,所以我们可以通过应用程序将它们组合起来,并收集我们遇到的所有失败。 Applicative Functors的弱点已经成为一种力量!Following watching Nick Partidge's presentation on deriving scalaz, I got to looking at this example, which is just awesome:
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))
I was trying to understand what the
<|*|>
method was doing, here is the source code:def <|*|>[B](b: M[B])(implicit t: Functor[M], a: Apply[M]): M[(A, B)] = <**>(b, (_: A, _: B))
OK, that is fairly confusing (!) - but it references the
<**>
method, which is declared thus: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)
So I have a few questions:
解决方案Type Constructors as Type Parameters
M
is a type parameter to one of Scalaz's main pimps, MA, that represents the Type Constructor (aka Higher Kinded Type) of the pimped value. This type constructor is used to look up the appropriate instances ofFunctor
andApply
, which are implicit requirements to the method<**>
.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] = ... }
What is a Type Constructor?
From the Scala Language Reference:
We distinguish between first-order types and type constructors, which take type parameters and yield types. A subset of first-order types called value types represents sets of (first-class) values. Value types are either concrete or abstract. Every concrete value type can be represented as a class type, i.e. a type designator (§3.2.3) that refers to a class1 (§5.3), or as a compound type (§3.2.7) representing an intersection of types, possibly with a refinement (§3.2.7) that further constrains the types of itsmembers. Abstract value types are introduced by type parameters (§4.4) and abstract type bindings (§4.3). Parentheses in types are used for grouping. We assume that objects and packages also implicitly define a class (of the same name as the object or package, but inaccessible to user programs).
Non-value types capture properties of identifiers that are not values (§3.3). For example, a type constructor (§3.3.3) does not directly specify the type of values. However, when a type constructor is applied to the correct type arguments, it yields a first-order type, which may be a value type. Non-value types are expressed indirectly in Scala. E.g., a method type is described by writing down a method signature, which in itself is not a real type, although it gives rise to a corresponding function type (§3.3.1). Type constructors are another example, as one can write type Swap[m[_, _], a,b] = m[b, a], but there is no syntax to write the corresponding anonymous type function directly.
List
is a type constructor. You can apply the typeInt
to get a Value Type,List[Int]
, which can classify a value. Other type constructors take more than one parameter.The trait
scalaz.MA
requires that it's first type parameter must be a type constructor that takes a single type to return a value type, with the syntaxtrait MA[M[_], A] {}
. The type parameter definition describes the shape of the type constructor, which is referred to as its Kind.List
is said to have the kind '* -> *
.Partial Application of Types
But how can
MA
wrap a values of typeValidation[X, Y]
? The typeValidation
has a kind(* *) -> *
, and could only be passed as a type argument to a type parameter declared likeM[_, _]
.This implicit conversion in object Scalaz converts a value of type
Validation[X, Y]
to aMA
:object Scalaz { implicit def ValidationMA[A, E](v: Validation[E, A]): MA[PartialApply1Of2[Validation, E]#Apply, A] = ma[PartialApply1Of2[Validation, E]#Apply, A](v) }
Which in turn uses a trick with a type alias in PartialApply1Of2 to partially apply the type constructor
Validation
, fixing the type of the errors, but leaving the type of the success unapplied.
PartialApply1Of2[Validation, E]#Apply
would be better written as[X] => Validation[E, X]
. I recently proposed to add such a syntax to Scala, it might happen in 2.9.Think of this as a type level equivalent of this:
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)
This lets you combine
Validation[String, Int]
with aValidation[String, Boolean]
, because the both share the type constructor[A] Validation[String, A]
.Applicative Functors
<**>
demands the the type constructorM
must have associated instances of Apply and Functor. This constitutes an Applicative Functor, which, like a Monad, is a way to structure a computation through some effect. In this case the effect is that the sub-computations can fail (and when they do, we accumulate the failures).The container
Validation[NonEmptyList[String], A]
can wrap a pure value of typeA
in this 'effect'. The<**>
operator takes two effectful values, and a pure function, and combines them with the Applicative Functor instance for that container.Here's how it works for the
Option
applicative functor. The 'effect' here is the possibility of failure.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)
In both cases, there is a pure function of type
(String, Int) => String
, being applied to effectful arguments. Notice that the result is wrapped in the same effect (or container, if you like), as the arguments.You can use the same pattern across a multitude of containers that have an associated Applicative Functor. All Monads are automatically Applicative Functors, but there are even more, like
ZipStream
.
Option
and[A]Validation[X, A]
are both Monads, so you could also usedBind
(aka flatMap):val result3 = oi flatMap { i => os map { s => s * i } } val result4 = for {i <- oi; s <- os} yield s * i
Tupling with `<|**|>`
<|**|>
is really similar to<**>
, but it provides the pure function for you to simply build a Tuple2 from the results.(_: A, _ B)
is a shorthand for(a: A, b: B) => Tuple2(a, b)
And beyond
Here's our bundled examples for Applicative and Validation. I used a slightly different syntax to use the Applicative Functor,
(fa ⊛ fb ⊛ fc ⊛ fd) {(a, b, c, d) => .... }
UPDATE: But what happens in the Failure Case?
what is happening to the Tuple2/Pair in the failure case?
If any of the sub-computations fails, the provided function is never run. It only is run if all sub-computations (in this case, the two arguments passed to
<**>
) are successful. If so, it combines these into aSuccess
. Where is this logic? This defines theApply
instance for[A] Validation[X, A]
. We require that the type X must have aSemigroup
avaiable, which is the strategy for combining the individual errors, each of typeX
, into an aggregated error of the same type. If you chooseString
as your error type, theSemigroup[String]
concatenates the strings; if you chooseNonEmptyList[String]
, the error(s) from each step are concatenated into a longerNonEmptyList
of errors. This concatenation happens below when twoFailures
are combined, using the⊹
operator (which expands with implicits to, for example,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 or Applicative, how shall I choose?
Still reading? (Yes. Ed)
I've shown that sub-computations based on
Option
or[A] Validation[E, A]
can be combined with eitherApply
or withBind
. When would you choose one over the other?When you use
Apply
, the structure of the computation is fixed. All sub-computations will be executed; the results of one can't influence the the others. Only the 'pure' function has an overview of what happened. Monadic computations, on the other hand, allow the first sub-computation to influence the later ones.If we used a Monadic validation structure, the first failure would short-circuit the entire validation, as there would be no
Success
value to feed into the subsequent validation. However, we are happy for the sub-validations to be independent, so we can combine them through the Applicative, and collect all the failures we encounter. The weakness of Applicative Functors has become a strength!这篇关于函数语法puzzler在scalaz中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!