Skip to content

Instantly share code, notes, and snippets.

@lbialy
Created November 26, 2024 11:20
Show Gist options
  • Save lbialy/6eb9c853a1bf82189fd05c8903b36c4e to your computer and use it in GitHub Desktop.
Save lbialy/6eb9c853a1bf82189fd05c8903b36c4e to your computer and use it in GitHub Desktop.
How to repack arbitrary tuple of Eithers into a case class
import java.util.UUID
import java.time.Instant
import scala.deriving.Mirror
case class MyCaseClass(
intField: Int,
longField: Long,
doubleField: Double,
stringField: String,
booleanField: Boolean,
charField: Char,
floatField: Float,
byteField: Byte,
shortField: Short,
uuidField: UUID,
instantField: Instant,
bigDecimalField: BigDecimal,
bigIntField: BigInt,
optionField: Option[String],
listField: List[Int],
setField: Set[Double],
mapField: Map[String, Int],
vectorField: Vector[Char],
arrayField: Array[Byte],
eitherField: Either[String, Int],
tupleField: (String, Int),
seqField: Seq[Double],
iterableField: Iterable[Boolean],
stringBuilderField: StringBuilder,
symbolField: Symbol
)
import java.util.UUID
import java.time.Instant
val allRights: (
Either[String, Int],
Either[String, Long],
Either[String, Double],
Either[String, String],
Either[String, Boolean],
Either[String, Char],
Either[String, Float],
Either[String, Byte],
Either[String, Short],
Either[String, UUID],
Either[String, Instant],
Either[String, BigDecimal],
Either[String, BigInt],
Either[String, Option[String]],
Either[String, List[Int]],
Either[String, Set[Double]],
Either[String, Map[String, Int]],
Either[String, Vector[Char]],
Either[String, Array[Byte]],
Either[String, Either[String, Int]],
Either[String, (String, Int)],
Either[String, Seq[Double]],
Either[String, Iterable[Boolean]],
Either[String, StringBuilder],
Either[String, Symbol]
) = (
Right(42),
Right(123456789L),
Right(3.14),
Right("Scala 3"),
Right(true),
Right('A'),
Right(2.71f),
Right(127.toByte),
Right(32767.toShort),
Right(UUID.randomUUID()),
Right(Instant.now()),
Right(BigDecimal(12345.6789)),
Right(BigInt(987654321)),
Right(Some("Option Value")),
Right(List(1, 2, 3)),
Right(Set(1.1, 2.2, 3.3)),
Right(Map("key1" -> 1, "key2" -> 2)),
Right(Vector('x', 'y', 'z')),
Right(Array(1.toByte, 2.toByte, 3.toByte)),
Right(Right(99)),
Right(("tupleValue", 123)),
Right(Seq(1.0, 2.0, 3.0)),
Right(Iterable(true, false, true)),
Right(new StringBuilder("StringBuilder Value")),
Right(Symbol("symbolValue"))
)
val someLefts: (
Either[String, Int],
Either[String, Long],
Either[String, Double],
Either[String, String],
Either[String, Boolean],
Either[String, Char],
Either[String, Float],
Either[String, Byte],
Either[String, Short],
Either[String, UUID],
Either[String, Instant],
Either[String, BigDecimal],
Either[String, BigInt],
Either[String, Option[String]],
Either[String, List[Int]],
Either[String, Set[Double]],
Either[String, Map[String, Int]],
Either[String, Vector[Char]],
Either[String, Array[Byte]],
Either[String, Either[String, Int]],
Either[String, (String, Int)],
Either[String, Seq[Double]],
Either[String, Iterable[Boolean]],
Either[String, StringBuilder],
Either[String, Symbol]
) = (
Left("Invalid Int"),
Right(123456789L),
Left("Invalid Double"),
Right("Scala 3"),
Left("Invalid Boolean"),
Right('A'),
Left("Invalid Float"),
Right(127.toByte),
Right(32767.toShort),
Left("Invalid UUID"),
Right(Instant.now()),
Left("Invalid BigDecimal"),
Right(BigInt(987654321)),
Left("Invalid Option"),
Right(List(1, 2, 3)),
Left("Invalid Set"),
Right(Map("key1" -> 1, "key2" -> 2)),
Left("Invalid Vector"),
Right(Array(1.toByte, 2.toByte, 3.toByte)),
Left("Invalid Either"),
Right(("tupleValue", 123)),
Left("Invalid Seq"),
Right(Iterable(true, false, true)),
Left("Invalid StringBuilder"),
Right(Symbol("symbolValue"))
)
import scala.compiletime.{erasedValue, summonInline, constValueTuple, error}
import scala.deriving.Mirror
def extractRights[T <: Tuple](tuple: T): Array[Any] =
def process(tuple: Tuple): List[Any] = tuple match
case EmptyTuple => Nil
case head *: tail =>
head match
case Right(value) => value :: process(tail)
case Left(error) =>
throw IllegalArgumentException(s"Found a Left: $error")
process(tuple).toArray
// Define a type-level operation to extract the right-hand type from Either
type UnwrapEither[T] = T match
case Either[_, r] => r
inline def unpackAndConstruct[A <: Product, T <: Tuple](
values: T
): Either[String, A] =
inline summonInline[Mirror.ProductOf[A]] match
case m: Mirror.ProductOf[A] =>
type CaseClassFieldTypes = m.MirroredElemTypes
type UnwrappedTupleTypes = Tuple.Map[T, UnwrapEither]
inline erasedValue[CaseClassFieldTypes] match
case _: UnwrappedTupleTypes =>
try
val args = extractRights(values)
Right(m.fromProduct(Tuple.fromArray(args)))
catch case e: IllegalArgumentException => Left(e.getMessage)
case _ => error("Provided tuple does not match the case class")
@main def main =
println(unpackAndConstruct[MyCaseClass, allRights.type](allRights))
println(unpackAndConstruct[MyCaseClass, someLefts.type](someLefts))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment