Scala: Future, zip, and recover - scala

The following code compiles and works as expected...
def find1(selector: JsValue): Future[Seq[String]]
def find2(selector: JsValue): Future[Seq[String]]
find1(Json.obj("name" -> "Joe")) zip
find2(Json.obj("name" -> "Tim")) map { case (result1, result2) =>
val result = result1 ++ result2
...
}
... but if I add recover to handle possible errors like this...
find1(Json.obj("name" -> "Joe")) zip
find2(Json.obj("name" -> "Tim")) map { case (result1, result2) =>
val result = result1 ++ result2
...
}.recover { case e =>
...
}
... I always get the following error:
[error] /home/j3d/test/TestController.scala:558: missing parameter type for expanded function
[error] The argument types of an anonymous function must be fully known. (SLS 8.5)
[error] Expected type was: ?
[error] find2(Json.obj("name" -> "Tim")) map { case (result1, result2) =>
[error] ^
[error] one error found
I've tried to specify the types of result1 and result2 like this...
find1(Json.obj("name" -> "Joe")) zip
find2(Json.obj("name" -> "Tim")) map { case (result1: Seq[String], result2: Seq[String]) =>
val result = result1 ++ result2
...
}.recover { case e =>
...
}
... but nothing changes, i.e. it only compiles without recover. Am I missing something?

use the operator notation throughout.
find1(Json.obj("name" -> "Joe")) zip
find2(Json.obj("name" -> "Tim")) map { case (result1, result2) =>
val result = result1 ++ result2
...
} recover { case e => // removed the '.'
...
}

Related

scala.concurrent.Future[play.api.mvc.Result] required: play.api.mvc.Result

I want an Action.async that (1) try to get values from the DB. If the DB is not available, it will try to connect to another resource and (2) get the values from there. Because the two resources that I am using return Future, I am separating them with "recover" keyword. I am not sure if it is the best way..... But the statement inside the recovery{} has a type mismatch error:
def show(url: String) = Action.async { implicit request: Request[AnyContent] =>
println("url: " + url)
val repositoryUrl = RepositoryUrl(url)
val repositoryId = RepositoryId.createFromUrl(url)
// Listing commits from the DB
val f: Future[Seq[Commit]] = commit.listByRepository(repositoryId.toString())
f.map { f: Seq[Commit] =>
val json = JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(f)))
Ok(json)
}.recover {
case e: scala.concurrent.TimeoutException =>
// InternalServerError("timeout")
// Listing commits from the Git CLI
val github = rules.GitHub(repositoryUrl)
val seq: Future[Seq[Commit]] = github.listCommits
seq.map { seq: Seq[Commit] =>
val json = JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(seq)))
Ok(json)
}
}
}
I am getting the error type mismatch; found : scala.concurrent.Future[play.api.mvc.Result] required: play.api.mvc.Result on the line seq.map { seq: Seq[Commit] =>. How can I return another result if I have a failure from my future?
Thanks!
recover wraps plain result in Future for you (analogue of map), while recoverWith expects Future as the result (analogue of flatMap). (https://stackoverflow.com/a/36585703/5794617). So, you should use recoverWith:
def show(url: String): EssentialAction = Action.async { implicit request: Request[AnyContent] =>
// This future will throw ArithmeticException because of division to zero
val f: Future[Seq[Int]] = Future.successful(Seq[Int](1, 2, 3, 4 / 0))
val fResult: Future[JsObject] = f.map { r =>
JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(r)))
}.recoverWith {
case e: ArithmeticException =>
val seq: Future[Seq[Int]] = Future.successful(Seq(1, 2, 3, 4))
seq.map { seq: Seq[Int] =>
JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(seq)))
}
}
fResult.map { r =>
Ok(r)
}
}
Scala Future.recover has a signature of
def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U]
Try to use recoverWith instead
def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U]

Akka Http client type mismatch

Can anyone tell me why I'm getting the following error?:
[error] HttpClient.scala:117: type mismatch;
[error] found : akka.stream.scaladsl.Sink[(akka.http.scaladsl.model.StatusCode, String),scala.concurrent.Future[(akka.http.scaladsl.model.StatusCode, String)]]
[error] required: akka.stream.Graph[akka.stream.SinkShape[(akka.http.scaladsl.model.StatusCode, String)],scala.concurrent.Future[akka.http.scaladsl.model.StatusCode]]
[error] source.via(flow).runWith(Sink.head)
[error] ^
Here's the code:
implicit def map2entity: ToEntityMarshaller[Map[String, Any]] = mapMarshaller(MediaTypes.`application/json`)
def mapMarshaller(mediaType: MediaType.WithFixedCharset): ToEntityMarshaller[Map[String, Any]] =
Marshaller.withFixedContentType(mediaType) { m => HttpEntity(mediaType, JSONObject(m).toString()) }
def post(path: String, entity: Map[String, Any]): Future[StatusCode] = {
val uri = Uri(getResourceUri(path))
logger.info(s"httpPost: $uri")
Marshal(entity).to[RequestEntity] flatMap { e =>
val source = Source.single(HttpRequest(
uri = uri.path.toString,
method = HttpMethods.POST,
entity = e))
val flow = getConnection(uri.scheme)(uri.authority.host.address)
.mapAsync(10) { r =>
//(r.status -> Marshal(r.entity).to[String])
Unmarshal(r.entity).to[String].flatMap(s => Future(r.status -> s))
}
source.via(flow).runWith(Sink.head)
}
}
The materialized value of your sink (Future[(StatusCode, String)]) is different from the return type you declared in the function (Future[StatusCode]).
If you post function only needs to return the status code, you can change this call
.flatMap(s => Future(r.status -> s))
To this
.map(_ => r.status)

Iterate Map entries inside match statement

I am writing equals method for a Scala class where accumUpdates is of Map[Long, Any].
I tried the following:
override def equals(other: Any): Boolean = other match {
case that: DirectTaskResult[_] if (!this.valueBytes.equals(that.valueBytes)) => false
case that: DirectTaskResult[_] if (this.accumUpdates.size != that.accumUpdates.size) => false
case that: DirectTaskResult[_] => {
for ((key, value) <- this.accumUpdates) {
if (!value.equals(that.accumUpdates.get(key))) false
}
}
case _ => false
}
The above gave me:
TaskResult.scala:53: type mismatch;
[error] found : Unit
[error] required: Boolean
[error] for ((key, value) <- this.accumUpdates) {
[error] ^
[error] one error found
Can someone provide hint as to how the Map entries can be iterated ?
Thanks
Try something like:
value.filter(value => value._2.equals(value1.get(value._1))).isEmpty
Reason you get error is, with condition getting satisfied, you will return false, but what if your condition evaluates to true.
Isn't this is you want to do?
case class DirectTaskResult(accumUpdates: Map[Long, Any])
object IterateMap {
val accumUpdates = Map[Long, Any](1L -> "one", 2L -> 2, 3L -> 3)
def thirdCaseClauseOfEquals(that: DirectTaskResult) = {
accumUpdates.keys.forall { key =>
accumUpdates.get(key) == that.accumUpdates.get(key)
}
}
}
It succeeds with this tests:
val t = Map[Long, Any](1L -> "one", 2L -> 2, 3L -> 3)
assert(IterateMap.thirdCaseClauseOfEquals(DirectTaskResult(t)) == true)
assert(IterateMap.thirdCaseClauseOfEquals(DirectTaskResult(t + (4L -> "Four"))) == true)
assert(IterateMap.thirdCaseClauseOfEquals(DirectTaskResult(t - 1L)) == false)
Your iterating over the map values is fine, but your for-loop is not. For-loop always returns Unit. If you want to return something else than Unit, you must use for-yield construct. The other answers showed how to reformulate it - I especially like the solution with forAll which already has a for-loop built in.
Here is illustration of the difference between for and for-yield
scala> def a(mym:Map[_,_]) = {for ((k,v)<-mym) k}
a: (mym: Map[_, _])Unit
scala> def a(mym:Map[_,_]) = {for ((k,v)<-mym) yield k}
a: (mym: Map[_, _])scala.collection.immutable.Iterable[Any]

scala compiler complains weirdly when compiling a foldleft with scalaz function

I recently happen to work with scalaz >>=. I put all the methods which should be bind with >>= in a list and foldleft as follows,
val dataMap:Map[K,V]
def call[F](funcList:List[funcOb[K, V, F]]):Either[F,Seq[(K,Option[V])]] = {
type t[a] = Either[F,a]
funcList.
map(v => {
v.funcs.
foldLeft((v.name,dataMap.get(v.name)).right[F])( _ >>= _ )
}
).sequence[t,(K,Option[V])]
}
case class funcOb[K,V,F]( name:K,
funcs:List[(K,Option[V]) => Either[F, (K, Option[V])]] = List.empty )
now I'm getting a funny error complaining that the required is similar to found
...: type mismatch;
[error] found : (K, Option[V]) => Either[F,(K, Option[V])]
[error] required: (K, Option[V]) => Either[F,(K, Option[V])]
[error] foldLeft((v.name,dataMap.get(v.name)).right[F])( _ >>= _ )
I cannot understand this behavour. Is there anything missing?
It requires a Function1[(A, B), C]. Your list contains functions of type Function2[A, B, C]. So you need to convert the function to tupled form before you can apply it.
The following works:
def call[F](funcList: List[funcOb[K, V, F]]): Either[F, Seq[(K, Option[V])]] = {
type t[a] = Either[F, a]
funcList.
map(v => {
v.funcs.
foldLeft((v.name, dataMap.get(v.name)).right[F])(_ >>= _.tupled)
}
).sequence[t, (K, Option[V])]
}

How do I get rid of this type warning/error?

I have a script. It runs without warnings.
$ cat ~/tmp/so1.scala
import org.yaml.snakeyaml.Yaml
class JavaMapIteratorWrapper[K,V] (map: java.util.Map[K,V]) {
def foreach (f: Tuple2 [K, V] => Unit): Unit = {
val iter = map.entrySet.iterator
while (iter.hasNext) {
val entry = iter.next
f (entry.getKey, entry.getValue)
}
}
}
implicit def foreachJavaMap[K,V] (map: java.util.Map[K,V]): JavaMapIteratorWrapper[K,V] = new JavaMapIteratorWrapper[K,V](map)
val yaml = new Yaml;
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map [_, _] => {
for (entry <- map) {
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
}
$ scala -unchecked -classpath jar/snakeyaml-1.7.jar ~/tmp/so1.scala eg/default.yaml
(program output as expected)
I'd like to extract the loop into its own function. So I try that.
$ cat ~/tmp/so2.scala
import org.yaml.snakeyaml.Yaml
class JavaMapIteratorWrapper[K,V] (map: java.util.Map[K,V]) {
def foreach (f: Tuple2 [K, V] => Unit): Unit = {
val iter = map.entrySet.iterator
while (iter.hasNext) {
val entry = iter.next
f (entry.getKey, entry.getValue)
}
}
}
implicit def foreachJavaMap[K,V] (map: java.util.Map[K,V]): JavaMapIteratorWrapper[K,V] = new JavaMapIteratorWrapper[K,V](map)
val processMap = (map: java.util.Map [_, _]) => {
for (entry <- map) { // line 16
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
val yaml = new Yaml;
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map [_, _] => processMap (map)
}
$ scala -unchecked -classpath jar/snakeyaml-1.7.jar ~/tmp/so2.scala eg/default.yaml
(fragment of so2.scala):16: error: type mismatch;
found : map.type (with underlying type java.util.Map[_, _])
required: java.util.Map[_$1,_$2] where type _$2, type _$1
for (entry <- map) {
^
one error found
!!!
discarding <script preamble>
The loop being in its own function means it requires a more specific type. Okay.
I'll try with java.util.Map [AnyRef, AnyRef] instead of java.util.Map [_, _].
$ cat ~/tmp/so3.scala
import org.yaml.snakeyaml.Yaml
class JavaMapIteratorWrapper[K,V] (map: java.util.Map[K,V]) {
def foreach (f: Tuple2 [K, V] => Unit): Unit = {
val iter = map.entrySet.iterator
while (iter.hasNext) {
val entry = iter.next
f (entry.getKey, entry.getValue)
}
}
}
implicit def foreachJavaMap[K,V] (map: java.util.Map[K,V]): JavaMapIteratorWrapper[K,V] = new JavaMapIteratorWrapper[K,V](map)
val processMap = (map: java.util.Map [AnyRef, AnyRef]) => {
for (entry <- map) {
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
val yaml = new Yaml;
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map [AnyRef, AnyRef] => processMap (map) // line 26
}
$ scala -unchecked -classpath jar/snakeyaml-1.7.jar ~/tmp/so3.scala eg/default.yaml
(fragment of so3.scala):26: warning: non variable type-argument AnyRef in type pattern is unchecked since it is eliminated by erasure
case map: java.util.Map [AnyRef, AnyRef] => processMap (map)
^
one warning found
!!!
discarding <script preamble>
(program output as expected)
So now it runs, but it gives me a warning. How do I eliminate that warning?
Notes:
org.yaml.snakeyaml.Yaml is written in Java, so I can't use type manifests. (Can I?)
My real program uses several Java libraries, so I want to be warned when I make possibly false assumptions about what types
I'm being given. But how do I tell the compiler "yes, I've checked this, it's correct, don't warn me about it again"?
I'm using scala 2.7.7 (because that's the version that's packaged with Ubuntu).
You could try removing your custom wrapper to start with. The (2.8.1) Scala standard library already includes a wrapper to use Java collection types more idiomatically, in scala.collection.JavaConverters. (note: the scala. prefix is not needed when importing this)
I'd also make processMap a method instead of a function, and add type params:
import collection.JavaConverters._
def processMap[K,V](map: Map[K, V]): Unit = {
for (entry <- map) {
entry match {
case ("id", id: String) => System.out.println ("ID is " + id)
case (n: String, v: String) => System.out.println (n + " = " + v)
}
}
}
val yaml = new Yaml
(yaml load (io.Source.fromFile(argv(0)).mkString)) match {
case map: java.util.Map[_, _] => processMap(map.asScala)
}
Note the asScala method on the second to last line...
When dealing with Java/Scala interop, it's generally a best practice to convert from Java to Scala collections at the earliest opportunity, and to convert back as late as possible.
You must be using Scala 2.7.X. If you use 2.8.1, your example with Map[_,_] works fine.
If you need to use 2.7.X, try converting your processMap value into a method:
def processMap[K,V] = (map: java.util.Map[K,V]) => {...}
That seemed to compile for me, but note that I "stubbed" the parts using the YAML library. I used:
val m1 = new java.util.HashMap[String,String]
m1.put("one", "1")
m1.put("id", "123")
m1.put("two", "2")
m1 match {
case map: java.util.Map [_, _] => processMap (map)
}