Consider this situation, I have a bunch of services that all need to check the input and handle errors.
val log = Logger("access")
def service1(){input=>
try{
val id = input.split(",")(0).toInt
val value = input.split(",")(1)
//do something
} catch {
case e1: NumberFormatException => log.info("number format is wrong")
case e2: ArrayIndexOutOfBoundsException=> log.info("not enough arguments")
}
}
I want to write a method that handles this common part for every service. I could do it this way:
def common(input:String, log:Logger, action:(Int)=>String):String={
try{
val id = input.split(",")(0).toInt
val value = input.split(",")(1)
action(id)
} catch {
case e1: NumberFormatException => log.info("number format is wrong")
case e2: ArrayIndexOutOfBoundsException=> log.info("not enough arguments")
}
}
Then the service function looks like this:
def service1(){input=> common(input, log, id=>{
//do something return a string
})
}
Is there a way to skip the parameters in common so that it looks more elegant like map in collections?
common(id=>{ //... })
import com.typesafe.scalalogging.StrictLogging
class MyService extends AbstractService {
def service1(input: String): String = common(input) {
id =>
id.toString
}
def service2(input: String): String = common(input) {
id =>
id.toString.toLowerCase
}
}
trait AbstractService extends StrictLogging {
def common(input: String)(f: Int => String): String = {
try {
val id = input.split(",")(0).toInt
f(id)
} catch {
case e1: NumberFormatException =>
logger.error("number format is wrong",e1)
"" //???
case e2: ArrayIndexOutOfBoundsException =>
logger.error("not enough arguments",e2)
"" //???
}
}
}
If input is specific you have to put it as input. Otherwise define method def input:String in trait and provide implementation in service.
Related
The following Scala code uses cats EitherT to wrap results in a Future[Either[ServiceError, T]]:
package com.example
import com.example.AsyncResult.AsyncResult
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
class ExternalService {
def doAction(): AsyncResult[Int] = {
AsyncResult.success(2)
}
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
result.recoverWith {
case ex: Throwable =>
println("recovered exception")
AsyncResult.success(99)
}
}
}
object ExceptionExample extends App {
private val me = new ExceptionExample()
private val result = me.callService()
result.value.map {
case Right(value) => println(value)
case Left(error) => println(error)
}
}
AsyncResult.scala contains:
package com.example
import cats.data.EitherT
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object AsyncResult {
type AsyncResult[T] = EitherT[Future, ServiceError, T]
def apply[T](fe: => Future[Either[ServiceError, T]]): AsyncResult[T] = EitherT(fe)
def apply[T](either: Either[ServiceError, T]): AsyncResult[T] = EitherT.fromEither[Future](either)
def success[T](res: => T): AsyncResult[T] = EitherT.rightT[Future, ServiceError](res)
def error[T](error: ServiceError): AsyncResult[T] = EitherT.leftT[Future, T](error)
def futureSuccess[T](fres: => Future[T]): AsyncResult[T] = AsyncResult.apply(fres.map(res => Right(res)))
def expectTrue(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, true, err)
def expectFalse(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, false, err)
}
ServiceError.scala contains:
package com.example
sealed trait ServiceError {
val detail: String
}
In ExceptionExample, if it call service.doAction() it prints 2 as expected, but if it call service.doException() it throws an exception, but I expected it to print "recovered exception" and "99".
How do I recover from the exception correctly?
That is because doException is throwing exception inline. If you want to use Either, you have to return Future(Left(exception)) rather than throwing it.
I think, you are kinda overthinking this. It does not look like you need Either here ... or cats for that matter.
Why not do something simple, like this:
class ExternalService {
def doAction(): Future[Int] = Future.successful(2)
def doException(): AsyncResult[Int] = {
println("do exception")
Future.failed(NullPointerException("run time exception"))
// alternatively: Future { throw new NullPointerExceptioN() }
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
// Note: the aboive is equivalent to just
// val result = service.doException
// You can write it as a chain without even needing a variable:
// service.doException.recover { ... }
result.recover { case ex: Throwable =>
println("recovered exception")
Future.successful(99)
}
}
I tend to agree that it seems a bit convoluted, but for the sake of the exercise, I believe there are a couple of things that don't quite click.
The first one is the fact that you are throwing the Exception instead of capturing it as part of the semantics of Future. ie. You should change your method doException from:
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
To:
def doException(): AsyncResult[Int] = {
println("do exception")
AsyncResult(Future.failed(new NullPointerException("run time exception")))
}
The second bit that is not quite right, would be the recovery of the Exception. When you call recoverWith on an EitherT, you're defining a partial function from the Left of the EitherT to another EitherT. In your case, that'd be:
ServiceError => AsyncResult[Int]
If what you want is to recover the failed future, I think you'll need to explicitly recover on it. Something like:
AsyncResult {
result.value.recover {
case _: Throwable => {
println("recovered exception")
Right(99)
}
}
}
If you really want to use recoverWith, then you could write this instead:
AsyncResult {
result.value.recoverWith {
case _: Throwable =>
println("recovered exception")
Future.successful(Right(99))
}
}
I have a method which returns Either[Exception, String]
class A {
def validate(a: Any) = {
case a: String => Left(...some.. exception)
case a: Any => Right(a)
}
}
class B(a: A) {
def callValidate(any: Any) = {
a.validate(any)
}
}
Now I write tests for class B and I stub method validate
class BTest {
val param: Any = "22"
val a = mock[A]
(a.validate _).expects(param).returning(....someValue...) // . this value should be Right(....) of either function.
}
Is it possible to stub it that way to return Right(.....) of Either function ?
As B is taking the object of a you can make a new object of A in BTest class and override the method validate to return whatever you want once return Right(a) and to cover the Left part return Left(a).
class BTest {
val param: Any = "22"
val a = new A{
override def validate(a:Any) = case _ => Right(a)
}
(a.validate _).expects(param).returning(Right("22"))
}
or you can do like this. As DarthBinks911 suggested.
(a.validate _).expects(param).returning(Right("a"))
this will work fine in the given scenario but if you do something like mockObject.something then it will give you NullPointerException. I would suggest you to override the validate method and return what ever you want.
Im trying to understand basics of serialization in Scala. When i run the first example below I get the following output on the last line: res1: A.Mao = A$$anonfun$main$1$Mao$1#78e67e0a
#SerialVersionUID(1L)
class Poo(val aa:Int) extends Serializable {
override def toString() = "Hola"
}
#SerialVersionUID(1L)
class Mao(val hi: Poo) extends Serializable
def serialize() = {
val test = new Mao(new Poo(1))
try{
val fout = new FileOutputStream("c:\\misc\\address.ser");
val oos = new ObjectOutputStream(fout);
oos.writeObject(test);
oos.close();
System.out.println("Done");
}catch {
case ex => ex.printStackTrace();
}
}
serialize()
def ReadObjectFromFile[A](filename: String)(implicit m:scala.reflect.Manifest[A]): A = {
val input = new ObjectInputStream(new FileInputStream(filename))
val obj = input.readObject()
obj match {
case x if m.erasure.isInstance(x) => x.asInstanceOf[A]
case _ => sys.error("Type not what was expected when reading from file")
}
}
ReadObjectFromFile[Mao]("c:\\misc\\address.ser")
If I change the example and use case classes instead things works as expected with the output
res1: A.Mao = Mao(Hola)
case class Poo(val aa:Int) {
override def toString() = "Hola"
}
case class Mao(val hi: Poo)
def serialize() = {
val test = new Mao(new Poo(1))
try{
val fout = new FileOutputStream("c:\\misc\\address.ser");
val oos = new ObjectOutputStream(fout);
oos.writeObject(test);
oos.close();
System.out.println("Done");
}catch {
case ex => ex.printStackTrace();
}
}
def ReadObjectFromFile[A](filename: String)(implicit m:scala.reflect.Manifest[A]): A = {
val input = new ObjectInputStream(new FileInputStream(filename))
val obj = input.readObject()
obj match {
case x if m.erasure.isInstance(x) => x.asInstanceOf[A]
case _ => sys.error("Type not what was expected when reading from file")
}
}
ReadObjectFromFile[Mao]("c:\\misc\\address.ser")
So my questions are:
What do I need to do to get class to give the same output as case class?
Why does case class work without adding any explicit information about serialization?
This has nothing to do with the de/serialization (which seems correct) - it's just the way the result is displayed:
Scala REPL (and Worksheets) use the value's toString method to display it. Case classes override the default toString() method, therefore the output is displayed nicely (as expected). For non-case classes, the defeault implementation of Object.toString() is called, and results in the class name and address that you see.
You can implement toString for the non-case class too, to get the same result:
class Mao(val hi: Poo) extends Serializable {
override def toString = s"Mao($hi)"
}
// program prints:
// Done
// Mao(Hola)
I'm trying to implement an implicit materializer as described here: http://docs.scala-lang.org/overviews/macros/implicits.html
I decided to create a macro that converts a case class from and to a String using quasiquotes for prototyping purposes. For example:
case class User(id: String, name: String)
val foo = User("testid", "foo")
Converting foo to text should result in "testid foo" and vice versa.
Here is the simple trait and its companion object I have created:
trait TextConvertible[T] {
def convertTo(obj: T): String
def convertFrom(text: String): T
}
object TextConvertible {
import language.experimental.macros
import QuasiTest.materializeTextConvertible_impl
implicit def materializeTextConvertible[T]: TextConvertible[T] = macro materializeTextConvertible_impl[T]
}
and here is the macro:
object QuasiTest {
import reflect.macros._
def materializeTextConvertible_impl[T: c.WeakTypeTag](c: Context): c.Expr[TextConvertible[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val fields = tpe.declarations.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asMethod.accessed
}
val strConvertTo = fields.map {
field => q"obj.$field"
}.reduce[Tree] {
case (acc, elem) => q"""$acc + " " + $elem"""
}
val strConvertFrom = fields.zipWithIndex map {
case (field, index) => q"splitted($index)"
}
val quasi = q"""
new TextConvertible[$tpe] {
def convertTo(obj: $tpe) = $strConvertTo
def convertFrom(text: String) = {
val splitted = text.split(" ")
new $tpe(..$strConvertFrom)
}
}
"""
c.Expr[TextConvertible[T]](quasi)
}
}
which generates
{
final class $anon extends TextConvertible[User] {
def <init>() = {
super.<init>();
()
};
def convertTo(obj: User) = obj.id.$plus(" ").$plus(obj.name);
def convertFrom(text: String) = {
val splitted = text.split(" ");
new User(splitted(0), splitted(1))
}
};
new $anon()
}
The generated code looks fine, but yet I get the error value id in class User cannot be accessed in User in compilation while trying to use the macro.
I suspect I am using a wrong type for fields. I tried field.asMethod.accessed.name, but it results in def convertTo(obj: User) = obj.id .$plus(" ").$plus(obj.name ); (note the extra spaces after id and name), which naturally results in the error value id is not a member of User.
What am I doing wrong?
Ah, figured it out almost immediately after sending my question.
I changed the lines
val fields = tpe.declarations.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asMethod.accessed
}
to
val fields = tpe.declarations.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.name
}
which solved the problem.
The field you get with accessed.name has a special suffix attached to it, to avoid naming conflicts.
The special suffix is scala.reflect.api.StandardNames$TermNamesApi.LOCAL_SUFFIX_STRING, which has the value, you guessed it, a space char.
This is quite evil, of course.
Currently I have couple of methods that are very similar and I would like to merge them into 1 method. Here are the 2 methods
def toInt(attrType: String, attrValue: String): Int = {
attrType match {
case "N" => attrValue.toInt
case _ => -1
}
}
def toString(attrType: String, attrValue: String): String = {
attrType match {
case "S" => attrValue
case _ => ""
}
}
I am thinking there is an easier way to do this in Scala using generic?
You could do the following:
trait Converter[T] {
def convert(attrType: String, attrValue: String): T
}
object ConverterTest {
implicit object IntConverter extends Converter[Int] {
def convert(attrType: String, attrValue: String): Int = {
attrType match {
case "N" => attrValue.toInt
case _ => -1
}
}
}
implicit object StringConverter extends Converter[String] {
def convert(attrType: String, attrValue: String): String = {
attrType match {
case "S" => attrValue
case _ => ""
}
}
}
def to[T: Converter](attrType: String, attrValue: String): T = {
implicitly[Converter[T]].convert(attrType, attrValue)
}
def main(args: Array[String]) {
println(to[String]("S", "B"))
println(to[String]("N", "B"))
println(to[Int]("S", "23"))
println(to[Int]("N", "23"))
}
}
Its more code, and I couldn't get type inferencing to work, so it is probably of limited use.
But it is a single method plus a bunch of converters that can get controlled at the call site, so you get some extra flexibility.
Is it worth the effort? Depends on the actual use case.