我正在使用外部配置的某些系统上工作,并根据提供的配置执行一些操作。我具有以下特征(为简单起见,省略了方法):
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
在这里有用吗?
答案 0 :(得分:1)
https://scalafiddle.io/sf/Z2NGo9y/0
这是一个可能的解决方案,但这并不是最佳选择,但您可以通过引入诸如shape / magnolia / scalaz-deriving之类的东西来推导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"))