函数语法puzzler在scalaz中 [英] Function syntax puzzler in scalaz

查看:107
本文介绍了函数语法puzzler在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)

所以我有几个问题:


  1. 为什么这个方法似乎需要一个类型参数( M [B] )的高级类型,但可以传递一个验证(它有两个类型参数)?

  2. 语法(_:A,_:B) code>定义函数(A,B)=>第二种方法所期望的对[A,B] :失败情况下Tuple2 / Pair发生了什么?
  3. h3>

    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)
    }
    }

    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:

    1. How come the method appears to take a higher-kinded type of one type parameter (M[B]) but can get passed a Validation (which has two type paremeters)?
    2. 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!

    解决方案

    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 of Functor and Apply, 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 type Int 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 syntax trait 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 type Validation[X, Y]? The type Validation has a kind (* *) -> *, and could only be passed as a type argument to a type parameter declared like M[_, _].

    This implicit conversion in object Scalaz converts a value of type Validation[X, Y] to a 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)
    }
    

    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 a Validation[String, Boolean], because the both share the type constructor [A] Validation[String, A].

    Applicative Functors

    <**> demands the the type constructor M 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 type A 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 used Bind (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 a Success. Where is this logic? This defines the Apply instance for [A] Validation[X, A]. We require that the type X must have a Semigroup avaiable, which is the strategy for combining the individual errors, each of type X, into an aggregated error of the same type. If you choose String as your error type, the Semigroup[String] concatenates the strings; if you choose NonEmptyList[String], the error(s) from each step are concatenated into a longer NonEmptyList of errors. This concatenation happens below when two Failures 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 either Apply or with Bind. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆