Writing typesafe code without boilerplate - scala

I'm working on some system that uses external configuration and does some action depending upon the configuration provided. I have the following traits (methods omitted for simplicity):
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])]
And the action:
def doUsefulAction[T, F](t1: Tr[T], t2: Trr[F], t3: Trrr[(T, F)]) = {
//...
}
The problem is the method invokation depends on the configuration:
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
}
The thing is there is tons of boilerplate with pattern matching. And this is just 3 traits. In case of 10 it becomes unreadable.
Is there a way to handle such a configuration yet staying types and avoid instanceOf?
Maybe macro can be useful here?

https://scalafiddle.io/sf/Z2NGo9y/0
This is a possible solution but it's not optimal yet you could eliminate more boilerplate by introducing something like shapeless/magnolia/scalaz-deriving to derive the implementations for the fromString implementations.
But really Validated and Applicative are your friends here
EDIT: As requested the code here
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"))

Related

Scala Play WebSocket - One Actor, multiple message types

What I want to do is basically 1:1 this: Scala Play Websocket - use one out actor to send both: Array[Byte] and String messages
Sadly the API has changed a lot since 2.4 (I am on 2.6.0-M4 right now).
What I tried (does not compile for obvious reasons):
WebSocket.accept[WSMessage, WSMessage]
{
request =>
ActorFlow.actorRef
{
out => WebSocketActor.props(out)
}
}
sealed trait WSMessage
case class StringMessage(s: String) extends WSMessage
case class BinaryMessage(a: Array[Byte]) extends WSMessage
case class JsonMessage(js: JsValue) extends WSMessage
object MyMessageFlowTransformer
{
implicit val WSMessageFlowTransformer: MessageFlowTransformer[WSMessage, WSMessage] =
{
new MessageFlowTransformer[WSMessage, WSMessage]
{
def transform(flow: Flow[WSMessage, WSMessage, _]) =
{
AkkaStreams.bypassWith[Message, WSMessage, Message](Flow[Message] collect
{
case StringMessage(s) => Left(s)
case BinaryMessage(b) => Left(b)
case JsonMessage(j) => Left(j)
case _ => Right(CloseMessage(Some(CloseCodes.Unacceptable)))
})(flow map WSMessage.apply)
}
}
}
}
I am somewhat lost. play.api.http.websocket.Message is a sealed trait, and probably for a good reason...
Define a MessageFlowTransformer[Either[String, Array[Byte]], Either[String, Array[Byte]]]:
type WSMessage = Either[String, Array[Byte]]
implicit val mixedMessageFlowTransformer: MessageFlowTransformer[WSMessage, WSMessage] = {
new MessageFlowTransformer[WSMessage, WSMessage] {
def transform(flow: Flow[WSMessage, WSMessage, _]) = {
AkkaStreams.bypassWith[Message, WSMessage, Message](Flow[Message].collect {
case BinaryMessage(data) => Left(Right(data.toArray))
case TextMessage(text) => Left(Left(text))
})(flow map {
case Right(data) => BinaryMessage.apply(ByteString.apply(data))
case Left(text) => TextMessage.apply(text)
})
}
}
}

Elegant Handling of Scala Future[Either]]

I have a type whose shape is like this:
val myType: Future[Either[MyError, TypeA]] = // some value
I know that I could pattern match on this and get to the Right or Left type, but the problem is that I would have to nest my pattern matching logic. I'm looking for much more elegant way of handling this? Any suggestions?
If you encode your MyError as an exception, you don't need the Either anymore and can simply patternMatch against the completion, or use a recoverWith to map it to another type:
myType.onComplete {
case Success(t) =>
case Failure(e) =>
}
To map your existing Either types you could do something like this:
case class MyException(e: MyError) extends Exception
def eitherToException[A](f: Future[Either[MyError,A]]): Future[A] = {
f.flatMap {
case Left(e) => Future.failed(MyException(e))
case Right(x) => Future.successful(x)
}
}
val myType2 = eitherToException(myType)
Alternatively, if MyError and TypeA are under your control, you could create a common super type and pattern match against that:
sealed trait MyResult
final case class MyError() extends MyResult
final case class TypeA() extends MyResult
myType.map {
case MyError() => ...
case TypeA() => ...
}
You can create custom extractor objects:
object FullSuccess {
def unapply[T](x: Try[Either[MyError, T]]) = x match {
case Success(Right(x)) => Some(x)
case _ => None
}
}
object PartSuccess {
def unapply[T](x: Try[Either[MyError, T]]) = x match {
case Success(Left(err)) => Some(err)
case _ => None
}
}
And
val myType: Future[Either[MyError, TypeA]] = // some value
myType.onComplete {
case FullSuccess(x) => ... // equivalent to case Success(Right(x))
case PartSuccess(x) => ... // equivalent to case Success(Left(x))
case Failure(e) => ...
}

Play Framework: How to implement proper error handling

I have a Play application with several modules, each of which has its own exception set. Here are three examples:
Module common:
package services.common
trait CommonErrors {
final case class NotFound(id: String) extends Exception(s"object $id not found")
final case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case NotFound => Results.NotFound(e.toJson)
case InvalidId => Results.BadRequest(e.toJson)
case _ => Results.InternalError(e.toJson)
}
}
Module auth:
package services.auth
trait AuthErrors {
final case class UserNotFound(e: NotFound) extends Exception(s"user ${e.id} not found")
final case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case UserNotFound => Results.NotFound(e.toJson)
case UserAlreadyExists => Results.BadRequest(e.toJson)
case _ => Results.InternalError(e.toJson)
}
}
Module other:
trait OtherErrors {
final case class AnotherError(s: String) extends Exception(s"another error: $s")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case AnotherError => Results.BadRequest(e.toJson)
...
case _ => Results.InternalError(e.toJson)
}
}
As you can see, each trait defines a set of exceptions and provides a method to convert that exception to a JSON response like this:
{
"status": 404,
"code": "not_found",
"message": "user 123456789123456789123456 not found",
"request": "https://myhost.com/users/123456789123456789123456"
}
What I'm trying to achieve is to have each module defining its exceptions, reusing the ones defined in the common module, and mixin the exception traits as needed:
object Users extends Controller {
val errors = new CommonErrors with AuthErrors with OtherErrors {
// here I have to override `toResult` to make the compiler happy
override def toResult(e: Exception) = super.toResult
}
def find(id: String) = Action { request =>
userService.find(id).map { user =>
Ok(success(user.toJson))
}.recover { case e =>
errors.toResult(e) // this returns the appropriate result
}
}
}
If you look at how I've overridden toResult, I always return super.toResult, which corresponds to the implementation contained in trait OtherErrors... and this implementation might miss some patterns that are expected to be found in CommonErrors.toResult.
For sure I'm missing something... so the question is: what's the design pattern to fix the issue with multiple implementations of toResult?
You could use Stackable Trait pattern. I'll replaced your .toJson with .getMessage for simplification reason:
define base trait:
trait ErrorsStack {
def toResult(e: Exception): Result = e match {
case _ => Results.InternalServerError(e.getMessage)
}
}
and stackable traits:
trait CommonErrors extends ErrorsStack {
case class NotFound(id: String) extends Exception(s"object $id not found")
case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
override def toResult(e: Exception): Result = e match {
case e: NotFound => Results.NotFound(e.getMessage)
case e: InvalidId => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
trait AuthErrors extends ErrorsStack {
case class UserNotFound(id: String) extends Exception(s"user $id not found")
case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
override def toResult(e: Exception): Result = e match {
case e: UserNotFound => Results.NotFound(e.getMessage)
case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
trait OtherErrors extends ErrorsStack {
case class AnotherError(s: String) extends Exception(s"another error: $s")
override def toResult(e: Exception): Result = e match {
case e: AnotherError => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
so if we have some stack
val errors = new CommonErrors with AuthErrors with OtherErrors
and defined some helper
import java.nio.charset.StandardCharsets.UTF_8
import play.api.libs.iteratee.Iteratee
import concurrent.duration._
import scala.concurrent.Await
def getResult(ex: Exception) = {
val res = errors.toResult(ex)
val body = new String(Await.result(res.body.run(Iteratee.consume()), 5 seconds), UTF_8)
(res.header.status, body)
}
following code
import java.security.GeneralSecurityException
getResult(errors.UserNotFound("Riddle"))
getResult(errors.UserAlreadyExists("Weasley"))
getResult(errors.NotFound("Gryffindor sword"))
getResult(errors.AnotherError("Snape's death"))
getResult(new GeneralSecurityException("Marauders's map"))
will produce reasonable output
res0: (Int, String) = (404,user Riddle not found)
res1: (Int, String) = (400,user identified by Weasley already exists)
res2: (Int, String) = (404,object Gryffindor sword not found)
res3: (Int, String) = (400,another error: Snape's death)
res4: (Int, String) = (500,Marauders's map)
also we can refactor this code, pulling case classed from traits, and make function's more composable:
type Resolver = PartialFunction[Exception, Result]
object ErrorsStack {
val resolver: Resolver = {
case e => Results.InternalServerError(e.getMessage)
}
}
trait ErrorsStack {
def toResult: Resolver = ErrorsStack.resolver
}
object CommonErrors {
case class NotFound(id: String) extends Exception(s"object $id not found")
case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
val resolver: Resolver = {
case e: NotFound => Results.NotFound(e.getMessage)
case e: InvalidId => Results.BadRequest(e.getMessage)
}
}
trait CommonErrors extends ErrorsStack {
override def toResult = CommonErrors.resolver orElse super.toResult
}
object AuthErrors {
case class UserNotFound(id: String) extends Exception(s"user $id not found")
case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
val resolver: Resolver = {
case e: UserNotFound => Results.NotFound(e.getMessage)
case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
}
}
trait AuthErrors extends ErrorsStack {
override def toResult = AuthErrors.resolver orElse super.toResult
}
object OtherErrors {
case class AnotherError(s: String) extends Exception(s"another error: $s")
val resolver: Resolver = {
case e: AnotherError => Results.BadRequest(e.getMessage)
}
}
trait OtherErrors extends ErrorsStack {
override def toResult = OtherErrors.resolver orElse super.toResult
}

Extract class from Option[T] when in None clause

Assuming you have the following code
trait T {
}
case class First(int:Int) extends T
case class Second(int:Int) extends T
val a:Option[T] = Option(First(3))
val b:Option[Second] = None
def test[A](a:Option[A])(implicit manifest:Manifest[Option[A]]) = {
a match {
case Some(z) => println("we have a value here")
case None => a match {
case t:Option[First] => println("instance of first class")
case s:Option[Second] => println("instance of second class")
}
}
}
test(b)
How would you extract what type the enclosing A is if the option happens to be a None. I have attempted to do various combinations of manifests, but none of them seem to work, each time it complains about the types being eliminated with erasure, i.e.
non-variable type argument First in type pattern Option[First] is unchecked since it is eliminated by erasure
You can use a type tag, a modern replacement for Manifest:
import scala.reflect.runtime.universe._
trait T
case class First(int:Int) extends T
case class Second(int:Int) extends T
def test[A: TypeTag](a: Option[A]) = {
a match {
case Some(_) => println("we have a value here")
case None => typeOf[A] match {
case t if t =:= typeOf[First] => println("instance of First")
case t if t =:= typeOf[Second] => println("instance of Second")
case t => println(s"unexpected type $t")
}
}
}
Example
val a = Option(First(3)) // we have a value here
val b: Option[First] = None // instance of First
val c: Option[Second] = None // instance of Second
val d: Option[Int] = None // unexpected type Int
By the way, you are interested in the type of A (which is being eliminated by erasure), so even with manifests you need one on A and not on Option[A].

Scala's compile-time type constant to use pattern matching

I'm using algebraic data types (case objects) to represent types to use them in pattern maching.
sealed trait PrimitiveType
case object IntType extends PrimitiveType
case object LongType extends PrimitiveType
case object StringType extends PrimitiveType
...
def valueType(key: String): PrimitiveType = {
"NAME" => StringType
"AGE" => IntType
...
}
def read(db: Database, key: String): Unit = valueType(key) match {
case IntType => send(db.parseIntField(database, key))
case LongType => send(db.parseLongField(database, key))
case StringType => send(db.parseStringField(database, key))
...
}
But you know, there are already Int, Long, String, and so on in Scala.
I think it is a waste to define several case objects to represent these.
I want to change IntType, LongType, ... to Int.type, Long.type or whatever I can use in pattern matching.
What's efficient in this case?
There are classOf, typeOf, TypeTag, ClassManifest, ... but I can't see exact difference among them.
I'm using Scala 2.11, so TypeTags are OK if it is effective in this case.
For this case (i.e. you are only dealing with primitive types and don't care about generics) all of them would work fine. Except there's no point using Manifests in new projects. E.g. with typetags:
import scala.reflect.runtime.universe._
val TypeTagString = typeTag[String]
def valueType(key: String): TypeTag[_] = {
"NAME" => TypeTagString
"AGE" => TypeTag.Int
...
}
def read(db: Database, key: String): Unit = valueType(key) match {
case TypeTag.Int => send(db.parseIntField(database, key))
case TypeTag.Long => send(db.parseLongField(database, key))
case TypeTagString => send(db.parseStringField(database, key))
...
}
However, I'd consider going back to the original design and changing it a bit:
sealed trait PrimitiveType[T] {
def parseField(db: Database, key: String): T
}
case object IntType extends PrimitiveType[Int] {
def parseField(db: Database, key: String) = db.parseIntField(key)
}
...
def read(db: Database, key: String): Unit = valueType(key).parseField(db, key)
You can use TypeTag objects and pattern matching as follows:
import scala.reflect.runtime.universe._
val TypeTagString = typeTag[String]
def valueType(key: String): TypeTag[_] = key match {
case "NAME" => typeTag[String]
case "AGE" => TypeTag.Int
}
def read(key: String): Unit = valueType(key) match {
case TypeTag.Int => println("It is an Int")
case TypeTagString => println("It is a String")
// replaced db access with println for simplicity.
}
But fundamentally there is no advantage in using typetags here. It is similar to:
def valueTypes(key: String): Any = key match {
case "NAME" => "NAME"
case "AGE" => 0
}
def read(key: String): Unit = valueTypes(key) match {
case 0 => println("It is an Int")
case "NAME" => println("It is a String")
}
or simply:
def read(key: String): Unit = key match {
case "NAME" | "COUNTRY" | ... => println("A string")
case "AGE" => println("An Int")
}