Написание безопасного кода без шаблонного кода - PullRequest
0 голосов
/ 22 января 2019

Я работаю на некоторой системе, которая использует внешнюю конфигурацию и выполняет некоторые действия в зависимости от предоставленной конфигурации.У меня есть следующие черты (методы опущены для простоты):

  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
  }

Дело в том, что существует множество шаблонов с шаблонным соответствием.И это только 3 черты.В случае 10 это становится нечитаемым.

Есть ли способ обработать такую ​​конфигурацию, оставаясь при этом типами и избегая instanceOf?

Может быть, macro может быть полезен здесь?

1 Ответ

0 голосов
/ 22 января 2019

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

Это возможное решение, но оно не оптимально, но вы можете исключить больше шаблонов, введя что-то вроде бесформенного / магнолийного / скалярного извлечения, чтобы получить реализации для fromString реализаций.

Но на самом деле Validated и Applicative ваши друзья здесь

РЕДАКТИРОВАТЬ: В соответствии с просьбой код здесь

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"))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...