检查数字列表并累积错误

时间:2017-04-29 15:45:07

标签: scala validation scala-cats

假设我需要检查给定的数字列表是否以一个或多个1,一个或多个2以及一个或多个3开头。如果检查失败,我希望累积所有错误,例如

val check: List[Int] => Either[String, Unit] = ???

check(Nil)           // error : "expected 1", "expected 2", "expected 3"
check(List(1, 1, 3)) // error : "expected 2"
check(List(1, 1, 4)) // errors: "expected 2", "expected 3"
check(List(3, 4, 5)) // errors: "expected 1", "expected 2"
check(List(0, 0, 0)) // errors: "expected 1", "expected 2", "expected 3"

为了实施check,我正在撰写one类型的函数twothreeList[Int] => Either[String, List[Int]]

import cats._, cats.data._, cats.implicits._

def num(n: Int): List[Int] => Either[String, List[Int]] = _ match { 
  case x::xs => if (x == n) (xs dropWhile (_ == n)).asRight else s"expected $n".asLeft
  case _ => s"expected $n".asLeft
}

val one   = num(1)
val two   = num(2)
val three = num(3)

scala> one(Nil)
res70: Either[String,List[Int]] = Left(expected 1)

scala> one(List(1, 1, 1))
res71: Either[String,List[Int]] = Right(List())

scala> one(List(2, 1, 1, 1))
res72: Either[String,List[Int]] = Left(expected 1)

scala> one(List(2, 3, 1, 1, 1))
res73: Either[String,List[Int]] = Left(expected 1)

如何撰写函数onetwothree来构建check?我可以使用Validated以及cats的所有其他内容。

2 个答案:

答案 0 :(得分:2)

注意:以下解决方案几乎不使用cats。某些cats专家可能会缩短它。

我认为用num定义为你的目标是不可能实现的,因为它失去了状态:它会丢失在成功的情况下丢弃了多少项目。如果我们还需要返回列表的其余部分,使用(Option[String], List[Int])作为返回类型似乎更容易:

def num(n: Int): List[Int] => (Option[String], List[Int]) = _ match {
  case x :: xs => if (x == n) (None, (xs dropWhile (_ == n))) else (Some(s"expected $n"), xs)
  case empty: List[Int] => (Some(s"expected $n"), empty)
}

现在,您可以创建构成检查的内容,例如:

def composedCheck(list: List[Int], checks: List[(List[Int]) => (Option[String], List[Int])]): Either[List[String], List[Int]] = {
  val allChecksRes = checks.foldLeft((List.empty[String], list))((acc, check) => {
    val checkRes = check(acc._2)

    // shorter syntax but slower
    //val errors = checkRes._1.toList ++ acc._1
    // longer but without that much allocation
    val errors = if (checkRes._1.isDefined) checkRes._1.get :: acc._1 else acc._1

    (errors, checkRes._2)
  })
  if (allChecksRes._1.isEmpty) list.asRight else allChecksRes._1.reverse.asLeft
}

我认为返回Either[List[String], List[Int]]是最自然的事情,让你以任何你喜欢的方式进一步处理错误,但也保留数据(原始列表),万一一切都很好。

最后,您可以将check创建为

val one = num(1)
val two = num(2)
val three = num(3)

val check: List[Int] => Either[String, List[Int]] = l => composedCheck(l, List(one, two, three)).left.map(errors => errors.mkString(", "))

所有代码为一体:

import cats.implicits._


object CatsChecks extends App {


  def num(n: Int): List[Int] => (Option[String], List[Int]) = _ match {
    case x :: xs => if (x == n) (None, (xs dropWhile (_ == n))) else (Some(s"expected $n"), xs)
    case empty: List[Int] => (Some(s"expected $n"), empty)
  }


  def composedCheck(list: List[Int], checks: List[(List[Int]) => (Option[String], List[Int])]): Either[List[String], List[Int]] = {
    val allChecksRes = checks.foldLeft((List.empty[String], list))((acc, check) => {
      val checkRes = check(acc._2)

      // shorter syntax but slower
      //val errors = checkRes._1.toList ++ acc._1
      // longer but without that much allocation
      val errors = if (checkRes._1.isDefined) checkRes._1.get :: acc._1 else acc._1

      (errors, checkRes._2)
    })
    if (allChecksRes._1.isEmpty) list.asRight else allChecksRes._1.reverse.asLeft
  }


  val one = num(1)
  val two = num(2)
  val three = num(3)

  val check: List[Int] => Either[String, List[Int]] = l => composedCheck(l, List(one, two, three)).left.map(errors => errors.mkString(", "))


  println(check(Nil)) // error : "expected 1", "expected 2", "expected 3"
  println(check(List(1, 1, 3))) // error : "expected 2"
  println(check(List(1, 1, 4))) // errors: "expected 2", "expected 3"
  println(check(List(3, 4, 5))) // errors: "expected 1", "expected 2"
  println(check(List(0, 0, 0))) // errors: "expected 1", "expected 2", "expected 3"
  println(check(List(1, 1, 2, 3, 3, 4)))
}

答案 1 :(得分:1)

听起来你想要的是Apply[Either[String, Unit], ?].map3(one, two, three) { case (res1, res2, res3) => ... } ,它为你提供了一系列arity函数,允许你链接仿函数。

kind-projector

我使用<?php foreach ($category_features as $category_feature):?> <?php if( $category_feature['form_type'] == 'text' ){?> <div class="six wide field"> <label><?php echo $category_feature['feauture'];?></label> <input type="text" name="name-<?php echo $category_feature['id']?>"> </div> <?php } ?> <?php if( $category_feature['form_type'] == 'select' ){?> <div class="six wide field"> <label><?php echo $category_feature['feauture'];?></label> <select name="name-<?php echo $category_feature['id']?>"> <option value="">Choose</option> <?php foreach ($feauture_values as $feauture_value):?> <!-- how can i get the features' values --> <?php endforeach;?> </select> </div> <?php } ?> <?php endforeach;?> 插件无需手动计算out类型,但除非你想实际使用可组合的东西派生出最终的仿函数,否则你想要应用&#34;中介&#34;步骤应用风格,这应该是方式。