Problem using ValidationNel and ⊛ with more than 12 checks

92 views
Skip to first unread message

Andreas Joseph Krogh

unread,
May 30, 2016, 7:47:33 AM5/30/16
to scalaz
Hi all.

I have the follwing checks, like this:

def checkInvoiceNumber : ValidationNel[String, String] = invoice.invoiceNumber match {
case Some(v) => v.successNel
case None => "Missing invoice-number".failureNel
}

def checkSentDate : ValidationNel[String, LocalDate] = invoice.sentDate match {
case Some(v) => v.successNel
case None => "Missing invoice-data".failureNel
}

etc..
...

Then I use the ⊛ operator to accumulate errors:

(checkInvoiceNumber
⊛ checkSentDate
⊛ checkCompany
⊛ checkSourceCompany
⊛ checkInvoiceSource
⊛ checkBankAccount
⊛ checkCustAddressStreet
⊛ checkCustAddressPlace
⊛ checkCustAddressCity
⊛ checkCustAddressZip
⊛ checkSenderAddressStreet
⊛ checkSenderAddressPlace
).tupled match {
case Failure(msgList) => prinln(
msgList)
case Success((invoiceNumber, invoiceDate, company, sourceComp, invoiceSource, fromBankAccount
, custAddrStreet, custAddrPlace, custAddrCity, custAddrZip
, senderAddrStreet, senderAddrPlace
)) =>



This approach stops working when having more then 12 checks (because ApplicativeBuilder12 is the "last one").

How can I accomplish the same using a List of any length, preserving the types in Success so I can use them like the match-statement above?

Thanks.

--
Andreas

Andreas Joseph Krogh

unread,
May 30, 2016, 3:04:10 PM5/30/16
to scalaz
Here's a complete working example.

The question is how to make it work for arbitrary long list of checks, and preserve the X-type-parameter of ValidationNel[E,X], like below.

import org.joda.time.LocalDate
import scalaz.Scalaz._
import scalaz._

case class Comp(name: String)

case class Inv(invoiceNumber: Option[String], sentDate: Option[LocalDate], company: Option[Comp])

class ValidationTest {

def checkInvoiceNumber(invoice: Inv) : ValidationNel[String, String] = invoice.invoiceNumber match {

case Some(v) => v.successNel
case None => "Missing invoice-number".failureNel
}
   def checkSentDate(invoice: Inv) : ValidationNel[String, LocalDate] = invoice.sentDate match {
case Some(v) => v.successNel
case None => "Missing invoice-date".failureNel
}
def checkCompany(invoice: Inv): ValidationNel[String, Comp] = invoice.company match {
case Some(v) => v.successNel
case None => "Missing company".failureNel
}

def validate(): Unit = {
val invoice = Inv("aaaaaa".some, LocalDate.now().some, Option.empty[Comp])

(checkInvoiceNumber(invoice)
⊛ checkSentDate(invoice)
⊛ checkCompany(invoice)

).tupled match {
case Failure(msgList) =>
         case Success((invoiceNumber, sentDate, company)) =>
}
}

}

Andreas Joseph Krogh

unread,
May 31, 2016, 4:33:54 PM5/31/16
to scalaz
Here's a working solution:

import org.joda.time.LocalDate
import org.testng.annotations.Test

import scalaz.Scalaz._
import scalaz._

case class Comp(name: String)

case class Inv(invoiceNumber: Option[String], sentDate: Option[LocalDate], company: Option[Comp])

@Test
class ValidationTest {

def checkInvoiceNumber(invoice: Inv) : ValidationNel[String, String] = invoice.invoiceNumber match {
case Some(v) => v.successNel
case None => "Missing invoice-number".failureNel
}
def checkSentDate(invoice: Inv) : ValidationNel[String, LocalDate] = invoice.sentDate match {
case Some(v) => v.successNel
case None => "Missing invoice-date".failureNel
}
def checkCompany(invoice: Inv): ValidationNel[String, Comp] = invoice.company match {
case Some(v) => v.successNel
case None => "Missing company".failureNel
}

   val badInvoice = Inv("aaaaaa".some, LocalDate.now().some, Option.empty[Comp])
val goodInvoice = Inv("aaaaaa".some, LocalDate.now().some, Comp("Visena").some)

def crankMan(company: Comp, sentDate: LocalDate, invoiceNumber: String): Inv = {
Inv(invoiceNumber.some, sentDate.some, company.some)
}

def testInv(invoice: Inv): Unit = {
(checkInvoiceNumber(invoice)
<*> (checkSentDate(invoice)
<*> (checkCompany(invoice) map (crankMan _).curried
))) match {
case Failure(msgList) => println(msgList.toList.mkString("\n"))
case Success(inv) => print(s"Invoice: $inv")
}
}

def testFisk(): Unit = {
testInv(badInvoice)
testInv(goodInvoice)
}
}

Richard Wallace

unread,
May 31, 2016, 6:03:04 PM5/31/16
to sca...@googlegroups.com

Here's an approach I've been experimenting with that doesn't require all those nested parenthesis

import scalaz._
import Scalaz._

object Test {

  case class Person(name: String, age: Int, height: Double)

  implicit class RotcnufOps[A, B](self: A => B) {
    def pam[F[_]](f: F[A])(implicit F: Functor[F]): F[B] =
      F.map(f)(self)

    def <&>[F[_]](f: F[A])(implicit F: Functor[F]): F[B] =
      F.map(f)(self)
  }

  implicit class YlppaOps[F[_], A, B](self: F[A => B])(implicit F: Apply[F]) {
    def <@>(f: F[A]): F[B] =
      F.ap(f)(self)

    def pa(f: F[A]): F[B] =
      F.ap(f)(self)
  }

  val f = Person.apply _

  val dude: Option[Person] = f.curried <&> some("Dude") <@> some(35) <@> some(6.0)

  val dudette: Option[Person] =
    f.curried.
      pam(some("Dudette")).
      pa(some(35)).
      pa(some(5.7))
}

Hope that helps,
Rich

--
You received this message because you are subscribed to the Google Groups "scalaz" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scalaz+un...@googlegroups.com.
To post to this group, send email to sca...@googlegroups.com.
Visit this group at https://groups.google.com/group/scalaz.
For more options, visit https://groups.google.com/d/optout.

Stephen Compall

unread,
Sep 10, 2016, 4:52:27 AM9/10/16
to sca...@googlegroups.com
On 5/30/16 6:47 PM, Andreas Joseph Krogh wrote:
Then I use the ⊛ operator to accumulate errors:
(checkInvoiceNumber
   ⊛ checkSentDate
   ⊛ checkCompany
   ⊛ checkSourceCompany
   ⊛ checkInvoiceSource
   ⊛ checkBankAccount
   ⊛ checkCustAddressStreet
   ⊛ checkCustAddressPlace
   ⊛ checkCustAddressCity
   ⊛ checkCustAddressZip
   ⊛ checkSenderAddressStreet
   ⊛ checkSenderAddressPlace
   ).tupled match {
   case Failure(msgList) => prinln(msgList)
   case Success((invoiceNumber, invoiceDate, company, sourceComp, invoiceSource, fromBankAccount
   , custAddrStreet, custAddrPlace, custAddrCity, custAddrZip
   , senderAddrStreet, senderAddrPlace
      )) =>

This approach stops working when having more then 12 checks (because ApplicativeBuilder12 is the "last one").

How can I accomplish the same using a List of any length, preserving the types in Success so I can use them like the match-statement above?

The simplest way is to add more parentheses and .tupled calls.  Then you can add more parentheses in the same places to your case Success, and the pattern match should work just as well.

There is an hlist approach that is less ad hoc, but requires more setup; I'd suggest it only if you are already incorporating hlists into your code.

Greetings,

Stephen.

Andreas Joseph Krogh

unread,
Sep 10, 2016, 5:21:01 AM9/10/16
to scalaz
A problem with my .curried approach is that it will stop working when num-checks > 22, because .curried is a member of scala.FunctionX.
I'm really interested i a solution which will scale to more than 100 checks, so if your solution based on HLists helps, please share it.

Thanks.

--
Andreas
Reply all
Reply to author
Forward
0 new messages