I have an actor that waits for the results of a future. Calling onComplete of the future causes a compiler error:
error: constructor cannot be instantiated to expected type
[scalac] found : akka.actor.Status.Success
[scalac] required: scala.util.Try[Iterable[Any]]
[scalac] case Success(result: List[PCBInstanceStats]) => {
[scalac] ^
Actor's receive:
case "pcbStatus" => {
val future = Future.traverse(context.children)(x => {
(x ? "reportStatus")(5 seconds)
})
future.onComplete {
case Success(result: List[PCBInstanceStats]) => {
self ! result
}
}
Not sure how to provide the right type of parameter for this.
[scalac] found : akka.actor.Status.Success
That means the compiler sees your Success and thinks it's an akka.actor.Status.Success, when really you mean a scala.util.Success. You probably have an import somewhere that is importing the akka Success class.
Either remove the import for akka.actor.Status.Success, or resolve the ambiguity by either fully-qualifying the class, or using an import alias, e.g.
import scala.util.{Success => ScalaSuccess}
future.onComplete {
case ScalaSuccess(result) => ...
// or
case scala.util.Success(result) => ...
}
Related
Is it possible to pattern match on a lazy val, declared as a Try, like this?
lazy val kafkaProducer: Try[producer.KafkaProducer[Array[Byte], String]] = Try(kafkaProducerSettings.createKafkaProducer())
...
kafkaProducer.get match {
case Success(_) => Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, kafkaProducer.get))
case Failure(x) => Future.failed(x)
}
I'm getting this error:
constructor cannot be instantiated to expected type;
[error] found : akka.actor.Status.Success
[error] required: org.apache.kafka.clients.producer.KafkaProducer[Array[Byte],String]
[error] case Success(_) => Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, kafkaProducer.get))
Note, this alternative code works, but I'm not sure it's the "Scala way":
lazy val kafkaProducer: producer.KafkaProducer[Array[Byte], String] = kafkaProducerSettings.createKafkaProducer()
...
val tryAccessLazyKafkaProducer = Try(kafkaProducer)
if (tryAccessLazyKafkaProducer.isSuccess) {
Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, kafkaProducer))
} else {
Future.failed(tryAccessLazyKafkaProducer.failed.get)
}
It's definitely possible, you just have the wrong Success type imported:
found : akka.actor.Status.Success
You need scala.util.Success instead
One thing you mustn't do is call Try.get, which will explode if the returned type is a Failure. Instead, do:
import scala.util.Success
import scala.util.Failure
kafkaProducer match {
case Success(producer) => Source.single(producerRecord()).runWith(Producer.plainSink(kafkaProducerSettings, producer))
case failure: Failure => failure
}
lazy is just a language construct which makes sure the value is only ever evaluated once. The underlying type, whether lazy or not, is still a Try which you can do what you do with it.
I'm having a problem to return the correct type in a scala play controller method can someone give me a hint here? I'm using for comprehantion to deal with two service methods that returns a Future, and I would like to handle elegantly the result and the errors.
What is the best practice to do this?
def registerUser = Action { implicit request =>
Logger.info("Start play actoin")
RegisterForm.form.bindFromRequest.fold(
formWithErrors => {
BadRequest(views.html.register(formWithErrors))
},
formData => {
val registerResult = for {
reCaptchaOk <- registerUserService.checkRecaptcha(formData.gRecaptchaResponse)
userId <- registerUserService.registerUser(formData) if reCaptchaOk
} yield userId
registerResult.map(
result => Redirect(routes.DashboardController.dashboard).withSession("USER_ID" -> result.toString))
.recover{
e => handleRegisterError(e)
}
})
}
def handleRegisterError(cause: Throwable)(implicit request: Request[_]) : Result = {
val form = RegisterForm.form.bindFromRequest
cause match {
case dae: DataAccessException =>
val globalError = dae.getCause.asInstanceOf[PSQLException].getSQLState match {
case "23505" => GlobalMessages(Seq(GlobalMessage(Messages("errors.db.userAlreadyExists") ,ERROR)))
case _ => GlobalMessages(Seq(GlobalMessage(Messages("errors.system.error"),ERROR)))
}
BadRequest(views.html.register(form,globalError))
case _ =>
BadRequest(views.html.register(form))
}
the error:
[error] (compile:compileIncremental) Compilation failed
[info] Compiling 1 Scala source to C:\repos\scala\SocerGladiatorWeb\target\scala-2.11\classes...
[error] C:\repos\scala\SocerGladiatorWeb\app\controllers\RegisterController.scala:56: type mismatch;
[error] found : Throwable => play.api.mvc.Result
[error] required: PartialFunction[Throwable,?]
[error] e => handleRegisterError(e)
[error] ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
Short answer
You need a partial function to recover future failures:
def handleRegisterError(implicit request: Request[_]): PartialFunction[Throwable, Result] = {
case dae: DataAccessException =>
val form = RegisterForm.form.bindFromRequest
val globalError = dae.getCause.asInstanceOf[PSQLException].getSQLState match {
case "23505" => GlobalMessages(Seq(GlobalMessage(Messages("errors.db.userAlreadyExists"), ERROR)))
case _ => GlobalMessages(Seq(GlobalMessage(Messages("errors.system.error"), ERROR)))
}
BadRequest(views.html.register(form, globalError))
case _ =>
val form = RegisterForm.form.bindFromRequest
BadRequest(views.html.register(form))
}
then change the controller code to
registerResult
.map { result =>
Redirect(routes.DashboardController.dashboard).withSession("USER_ID" -> result.toString)
}
.recover {
handleRegisterError
}
Also note that you need an async action, i.e.
def registerUser = Action.async { implicit request =>
...
}
because you are not returning a Result but a Future[Result]. You can find more about actions in Play docs.
Details
If you look at the docs of the recover method of Future (see here) you'll see that it needs a pf: PartialFunction[Throwable, U].
Partial functions are just like normal functions but they might reject some values (for instance here, the recover method does not accept all exceptions, but only those specified in the body).
Defining a partial function needs a special syntax. It's very much like pattern matching but with no match expression.
Future(someAsyncWork).recover {
case my: MyException => ....
case _ => ....
}
Here we are using a partial recover function inline, so the type will be inferred automatically but if you want to define the recover as a separate function you need to explicitly state its type.
Advanced
The partial function syntax (pattern matching with no match keyword) is very concise and handy in most situations, but sometimes you need more than that.
For instance, note that using this syntax, we had to duplicate parts of the code (val form = RegisterForm.form.bindFromRequest) in the recover function.
Although in your case there might be better solutions but you can always convert a normal function to a partial function. First you need to define a function of the type Throwable => Option[Result] and then you can use Function#unlift to convert it to the desired partial function.
Also you can directly inherit from a PartialFunction and implement its two methods (apply and isDefinedAt).
Code below is a simplified version of the real code. We "inherited" the domain model case object FutTest and case class FutTest, which we can't modify. The actual domain models are served from a Database, so I believe the Future approach is valid, but it causes problems which I don't understand.
import org.scalatest.FunSpec
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
case object FutTest {
def create(sz: Int) = { FutTest(sz) }
}
case class FutTest(size: Int)
class FutureTest extends FunSpec {
def one(v: Int): Future[FutTest] = {
Future { FutTest.create(v) }
}
def two(t: FutTest) = {
Future { FutTest.create(t.size) }
}
def compileError1: Future[FutTest] = {
one(10).map(f => two(f))
}
def compileError2: Future[FutTest] = {
for { o <- one(10) } yield (two(o))
}
}
The error messages:
[INFO] Using incremental compilation
[INFO] Compiling 7 Scala sources and 5 .. target/test-classes...
[ERROR] domain.FutureTest.scala:25: type mismatch;
found : scala.concurrent.Future[domain.FutTest]
required: domain.FutTest
[ERROR] one(10).map(f => two(f))
[ERROR] ^
[ERROR] domain/FutureTest.scala:29: type mismatch;
found : scala.concurrent.Future[domain.FutTest]
required: domain.FutTest
[ERROR] for { o <- one(10) } yield (two(o))
I tried the above code with plain Int instead of FutTest and all is fine. Why is the compiler complaining and how can we solve this without touching the existing domain.
flatMap is what you want.
one(10).flatMap(f => two(f))
or
one(10).flatMap(two)
Using for comprehension,
for { o <- one(10); t <- two(o) } yield t
One() returns a Future and two() also returns a Future so you need to flatMap instead of map. When you map to two(), your result is Future[Future[FutTest]] and needs to be flattened.
Doing
one(10).flatMap(f => two(f))
should do the trick.
When using some code like the following:
scala> Future { null } onComplete { case Success(v) => v.toString }
Scala throws the following exception:
scala> java.lang.NullPointerException
at $line14.$read$$iw$$iw$$anonfun$2.apply(<console>:11)
at $line14.$read$$iw$$iw$$anonfun$2.apply(<console>:11)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
at scala.concurrent.impl.ExecutionContextImpl$$anon$3.exec(ExecutionContextImpl.scala:107)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
This would be OK, since I am not handling any exceptions. The problem is that my application hangs completely.
I am using concurrent.ExecutionContext.Implicits.global and I think onComplete is executed in this global execution context. The problem is that it seems like the execution context stops accepting any work and and the application just hangs.
Do I have to explicitly use try ... catch so that I protect my app in case something unexpected happens in onComplete?
Thank you
IIRC, this was an issue only in the very earliest implementation.
You can supply a handler or "reporter":
scala> import util._
import util._
scala> import concurrent._
import concurrent._
scala> ExecutionContext.fromExecutor(null, (t: Throwable) => println(s"Hi, $t"))
res0: scala.concurrent.ExecutionContextExecutor = scala.concurrent.impl.ExecutionContextImpl#221a3fa4
scala> implicit val x = res0
x: scala.concurrent.ExecutionContextExecutor = scala.concurrent.impl.ExecutionContextImpl#221a3fa4
scala> Future { null } onComplete { case Success(v) => v.toString }
<console>:16: warning: match may not be exhaustive.
It would fail on the following input: Failure(_)
Future { null } onComplete { case Success(v) => v.toString }
^
Hi, java.lang.NullPointerException
scala>
Everything is handled.
First of all, the NullPointerException you get has nothing to do with the future; it does not happen inside the Future block.
What you can do, is wrapping code that might return null in Option().
Your code would then look like this:
Future { Option(mightBeANull) } onComplete { case Success(v) => v.map(_.toString) }
I use Scala 2.9.1. I have a simple scala "interpreter":
import scala.tools.nsc.interpreter.IMain
import scala.tools.nsc.interpreter.Results.Result
import scala.tools.nsc.interpreter.Results.Success
object App {
def main(args: Array[String]) {
val interpreter = new IMain
val result:Result = interpreter.interpret(args(0))
result.toString() match {
case "Success" =>
{
var success = result.asInstanceOf[Success]
println(success.productElement(0))
};
case _ => println("very bad result");
}
}
}
When i try to compile it (maven) i get:
[ERROR] /home/koziolek/workspace/dsi/src/main/scala/pl/koziolekweb/scala/dsi/App.scala:15: error: not found: type Success
[INFO] var success = result.asInstanceOf[Success]
As you can see, the compiler said that there is no type Success, although I imported it.
Success is an object, not a class, you would need to cast it to its singleton type result.asInstanceOf[Success.type]. Obviously you are trying to work around not knowing how to do the pattern match. That would allow you to get the right result without casting:
import tools.nsc.interpreter.Results._
result match {
case Success => "yes!"
case Error => "no..."
case Incomplete => "you missed something"
}
If you want to get the resulting value of the interpreted expression in the case of success, see my reply in this post for more details.