在没有样板的情况下编写类型安全代码

时间:2019-01-22 14:50:08

标签: scala types type-safety

我正在使用外部配置的某些系统上工作,并根据提供的配置执行一些操作。我具有以下特征(为简单起见,省略了方法):

  sealed trait Tr[T]
  case object Tr1 extends Tr[String]
  case object Tr2 extends Tr[Int]
  case object Tr3 extends Tr[Array[Byte]]

  sealed trait Trr[T]
  case object Trr1 extends Trr[String]
  case object Trr2 extends Trr[Int]
  case object Trr3 extends Trr[Array[Byte]]


  trait Trrr[T]
  case object Trrr1 extends Trrr[(String, Int)]
  case object Trrr2 extends Trrr[(Int, String)]
  case object Trrr3 extends Trrr[(Int, Int)]
  case object Trrr4 extends Trrr[(String, String)]
  case object Trrr5 extends Trrr[(String, Array[Byte])]

动作:

  def doUsefulAction[T, F](t1: Tr[T], t2: Trr[F], t3: Trrr[(T, F)]) = {
    //...
  }

问题在于方法调用取决于配置:

  def invokeWithConfig[T1, T2, T3](cfgTr1: String, cfgTr2: String, cfgTr3: String) = cfgTr1 match {
    case "1" =>
      cfgTr2 match {
        case "1" =>
          cfgTr3 match {
            case "4" => doUsefulAction(Tr1, Trr1, Trrr4)
            case _ => throw new IllegalArgumentException
          }
        case "2" =>
          cfgTr3 match {
            case "1" => doUsefulAction(Tr1, Trr2, Trrr1)
            case _ => throw new IllegalArgumentException
          }
        case "3" =>
          cfgTr3 match {
            case "5" => doUsefulAction(Tr1, Trr3, Trrr5)
            case _ => throw new IllegalArgumentException
          }
        case _ => throw new IllegalArgumentException
      }
    case "2" =>
      //same boilerplate as above
    case "3" =>
      //same boilerplate as above
    case _ => throw new IllegalArgumentException
  }

问题在于,有大量具有模式匹配的样板。这只是三个特质。如果是10,则变得不可读。

有没有办法处理这样的配置却保持类型并避免使用instanceOf

也许macro在这里有用吗?

1 个答案:

答案 0 :(得分:1)

https://scalafiddle.io/sf/Z2NGo9y/0

这是一个可能的解决方案,但这并不是最佳选择,但您可以通过引入诸如shape / magnolia / scalaz-deriving之类的东西来推导fromString实现的实现来消除更多样板。

但是实际上ValidatedApplicative是您的朋友

编辑:按要求在此处输入代码

import cats._
import cats.implicits._
import cats.data.Validated._
import cats.data.ValidatedNel
import cats.data.NonEmptyList

sealed trait Tr[T]
case object Tr1 extends Tr[String]
case object Tr2 extends Tr[Int]
case object Tr3 extends Tr[Array[Byte]]

object Tr{
  def fromString(s:String):ValidatedNel[Throwable, Tr[_]] = s match {
    case "1" => Tr1.validNel
    case "2" => Tr2.validNel
    case "3" => Tr3.validNel
    case _ => new RuntimeException(s"$s is not a valid Tr").invalidNel
  }
}

sealed trait Trr[T]
case object Trr1 extends Trr[String]
case object Trr2 extends Trr[Int]
case object Trr3 extends Trr[Array[Byte]]

object Trr{
  def fromString(s:String):ValidatedNel[Throwable, Trr[_]] = s match {
    case "1" => Trr1.validNel
    case "2" => Trr2.validNel
    case "3" => Trr3.validNel
    case _ => new RuntimeException(s"$s is not a valid Trr").invalidNel
  }
}


trait Trrr[T, T1]
case object Trrr1 extends Trrr[String, Int]
case object Trrr2 extends Trrr[Int, String]
case object Trrr3 extends Trrr[Int, Int]
case object Trrr4 extends Trrr[String, String]
case object Trrr5 extends Trrr[String, Array[Byte]]

object Trrr{
  def fromString(s:String):ValidatedNel[Throwable, Trrr[_, _]] = s match {
    case "1" => Trrr1.validNel
    case "2" => Trrr2.validNel
    case "3" => Trrr3.validNel
    case "4" => Trrr4.validNel
    case "5" => Trrr5.validNel
    case _ => new RuntimeException(s"$s is not a valid Trrr").invalidNel
  }
}

def doUseful[T1, T2](tr:Tr[T1], trr:Trr[T2], trrr:Trrr[T1,T2]):String = "called"


def dispatch(s1:String, s2:String, s3:String):Either[Throwable, String] = (
    Tr.fromString(s1),
    Trr.fromString(s2),
    Trrr.fromString(s3),
  )
  .tupled
  .leftMap( 
    errs => new RuntimeException(
      Foldable[NonEmptyList].intercalate(errs.map(_.getMessage),"\n")
      )
    )
  .toEither
  .flatMap {
    case (a@Tr1, b@Trr2, c@Trrr1) => Right(doUseful(a,b,c))
    case _ => Left(new RuntimeException("non mapped possibility"))
    //note the line below won't compile because there's no valid combination of T1, T2 to call doUseful
    //case (a@Tr1, b@Trr2, c@Trrr4) => doUseful(a,b,c)
  }

println(dispatch("1", "2", "1"))
println(dispatch("1", "2", "15"))
println(dispatch("1", "20", "15"))