Functional puzzler syntax in scalaz

After watching Nick Partridge 's announcement of scalaz withdrawal, I had to look at this example, which is simply amazing:

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 tried to understand what the <|*|> method does, here is the source code:

 def <|*|>[B](b: M[B])(implicit t: Functor[M], a: Apply[M]): M[(A, B)] = <**>(b, (_: A, _: B)) 

OK, this is pretty confusing (!), But it refers to the <**> method, which is declared as follows:

 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:

  • How is it that the method accepts a higher type of one parameter of type ( M[B] ), but can be passed a Validation (which has two types of parameters)?
  • The syntax (_: A, _: B) defines the function (A, B) => Pair[A,B] , which the second method expects: what happens to Tuple2 / Pair in case of failure? There is no motorcade!
+50
scala functional-programming scalaz
Mar 30 '10 at 12:51
source share
1 answer

Type constructors as type parameters

M is a type parameter to one of Scalaz's main pimps, MA , which is a Type Constructor (aka Higher Kinded Type) of a fixed value. This type constructor is used to search for matching instances of Functor and Apply , which are implicit requirements for 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 types of constructors that accept type parameters and crop types. A subset of first-order types, called value types, are (first-class) values. The types of values ​​are either concrete or abstract. each concrete value type can be represented as a class type, i.e. a type (§3.2.3) that refers to class1 (§5.3) or as a composite type (§3.2.7) representing the intersection of types, possibly with refinement (§3.2 .7), which further inhibits the types of its members. Abstract value types are introduced by type parameters (§4.4) and abstract type bindings (§ 4.3). Brackets in types are used for grouping. Suppose that objects and packages also implicitly define a class (with the same name as the object or package, but are not available to user programs).

Obscene types fix identifier properties that are not values ​​(§3.3). For example, the type constructor (section 3.3.3) does not directly specify the type of values. However, when a type constructor is applied to the correct type arguments, it gives a first order type, which can be a value type. Obscene types expressed indirectly in Scala. For example, a type of a method is described by writing down the signature of the method, which in itself is not a real type, although it leads to an appropriate type function (§3.3.1). Type constructors are another example, since you can write the type Swap [m [_, _], a, b] = m [b, a], but there is no syntax for writing the corresponding function of the anonymous type directly.

List is a type constructor. You can apply an Int type to get a Value Type, List[Int] , which can classify a value. Other type constructors accept more than one parameter.

The scalaz.MA requires that the parameter of the first type must be a constructor of the type that takes one type to return the value type with the syntax trait MA[M[_], A] {} . A type parameter definition describes the form of a type constructor, which is called its type. It is said that List has the form * -> * .

Partial Type Application

But how MA wrap values ​​like Validation[X, Y] ? The Validation type has the form (* *) -> * and can only be passed as an argument to a type parameter declared as M[_, _] .

This implicit conversion to a Scalaz object converts a value of type Validation[X, Y] to 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 an alias of type PartialApply1Of2 to partially apply the constructor of the Validation type, setting the error type, but leaving the success type unapplied .

PartialApply1Of2[Validation, E]#Apply better written as [X] => Validation[E, X] . I recently suggested adding such syntax to Scala, this could happen in 2.9.

Think of it as equivalent to a type level:

 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) 

This allows you to combine Validation[String, Int] with Validation[String, Boolean] , because both share a constructor of the type [A] Validation[String, A] .

Applicative functors

<**> requires that a constructor of type M must have associated instances of Apply and Functor . This is an applicative functor, which, like Monad, is a way of structuring the calculation through some effect. In this case, the effect is that sub-computations may fail (and when they do, we accumulate failures).

The Validation[NonEmptyList[String], A] container can wrap a pure value of type A in this "effect." The <**> operator takes two effective values ​​and a pure function and combines them with the Applicative Functor instance for this container.

Here's how it works for the Option application 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 the type (String, Int) => String , applied to the effective arguments. Note that the result is wrapped in the same effect (or container, if you want) as arguments.

You can use the same template for multiple containers with the associated applicative functor. All monads are automatically applicative functors, but there are even more, for example ZipStream .

Option and [A]Validation[X, A] are Monads, so you can also use Bind (aka flatMap):

 val result3 = oi flatMap { i => os map { s => s * i } } val result4 = for {i <- oi; s <- os} yield s * i 

Tuple with `<| ** | > `

<|**|> really similar to <**> , but it provides a clean function to easily create Tuple2 from the results. (_: A, _ B) is short for (a: A, b: B) => Tuple2(a, b)

And further

Here are examples of Applicative and Validation . I used a slightly different syntax to use Applicative Functor, (fa ⊛ fb ⊛ fc ⊛ fd) {(a, b, c, d) => .... }

UPDATE: But what happens in the event of a failure?

What happens to Tuple2 / Pair in the event of a failure?

If any of the subcomplexes fails, the provided function never starts. It is launched only if all subcomputations (in this case, two arguments passed to <**> ) are successful. If so, he combines them into Success . Where is this logic? This defines the Apply instance for [A] Validation[X, A] . We require that type X must have Semigroup avaiable, which is a strategy for combining individual errors, each of types X , into an aggregate error of the same type. If you selected String as the error type, Semigroup[String] concatenates strings; if you select NonEmptyList[String] , errors (errors) from each step will be combined into longer NonEmptyList errors. This concatenation occurs below when two Failures are combined using the operator (which expands with implicits, e.g. 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 to choose?

Still reading? (Yes, Ed)

I have shown that sub-calculations based on Option or [A] Validation[E, A] can be combined with either Apply or Bind . When will you choose one by one?

When you use Apply , the calculation structure is fixed. All sub-calculations will be performed; the results of one cannot influence the others. Only a “pure” function has an overview of what happened. On the other hand, monadic calculations make it possible for the first to calculate the influence on later ones.

If we used the monodal verification structure, the first failure would be the short term completion of the entire verification, since there would be no Success value for the subsequent subsequent verification. However, we are pleased that the sub-validations are independent, so we can combine them using Applicative and collect all the failures that we encounter. The weakness of applicative functors has become a force!

+63
Mar 30 '10 at 15:20
source share



All Articles