For comprehension with optional collection iteration and yield - scala

I am not very versed in Scala and sincerily I found the documentation hard to try to figure this out, but I was wondering if someone could explain to me why the difference in compilation of the following statements:
I can easily iterate over a set of strings and yield the elements.
scala> for(name <- Set("asd", "123")) yield name
val res2: scala.collection.immutable.Set[String] = Set(asd, 123)
But i can't do it inline if the Set is inside an Option
scala> for(names <- Some(Set("asd", "123")); name <- names) yield (name)
^
error: type mismatch;
found : scala.collection.immutable.Set[String]
required: Option[?]

It happens because of for-yield is just syntactic sugar for flatMap, map and withFilter functions. So, your code:
for(names <- Some(Set("asd", "123")); name <- names) yield (name)
actually is the same as:
Some(Set("asd", "123")).flatMap{ names: Set[String] =>
names.flatMap{ name: String =>
name // return type is String, but Set[(of some )B] is expected
}
}// return type is Set[String] but Option[(of some) C] is expected
look at the Option flatMap function:
#inline final def flatMap[B](f: A => Option[B]): Option[B]
but your f returns Set[String]
as a result, compiler tell you about type missmatch (Set[String] != Option[?]):
error: type mismatch;
found : scala.collection.immutable.Set[String]
required: Option[?]
you should remember about type of the first statement in for-yield construction. In your case it's names <- Some(Set("asd", "123")). It has type Option[Set[String]], so you should use only Option[T] in x <- yourNextStatement lines (x should has Option[T] type).
In conclusion:
Be careful with mixing different container types in for-yield constructions. If you have some problems, just try to unwrap your for-yield into combination of flatMap, map, withFilter functions.
If you want to mix containers in for-yeild, you should start another for-yield for each-container type. For example:
for {
names <- Some(Set("asd", "123"))
// will return Option[String]
reducedNames <- (for {
name <- names // here will be used Seq flatMap function, not Option
} yield (name + "some_suffix"))
.reduceLeftOption(_ + _) // here we will get Option[String] from Seq[String]
} yield reducedNames // will return Option[String]

What do you want to happen for None? Assuming "nothing", you could do
val optionalSet = Some(Set("asd", "123"))
for (names <- optionalSet.getOrElse(Set.empty); name <- names) yield name

Related

Scala: composing queries inside for comprehension giving errors

I am trying to get this query right but getting errors. First, searchUser returns a sequence of matching UserEntries that contains unique id for user and for each of the userId, there is a second query obtains some other user info + address from another table.
Code:
def searchUsers(pattern: String) = auth.SecuredAction.async {
implicit request =>
usersService.searchUser(pattern) flatMap { usrList =>
for {
u <- usrList
ui <- usersService.getUsersWithAddress(u.id)
} yield {
Ok(views.html.UserList(ui))
}
}
}
Signatures for the APIs used:
def searchUser(pattern: String): Future[Seq[UserEntry]] = ...
def getUsersWithAddress(userId: Long): Future[Seq[(UserProfile, Seq[String])]] = ...
Error:
[error] modules/www/app/controllers/Dashboard.scala:68: type mismatch;
[error] found : scala.concurrent.Future[play.api.mvc.Result]
[error] required: scala.collection.GenTraversableOnce[?]
[error] ui <- usersService.getUsersWithAddress(u.id)
[error] ^
[error] one error found
[error] (www/compile:compileIncremental) Compilation failed
If I comment out line "u <- usrList" and hardcode a userid for the next line like "ui <- usersService.getUsersWithAddress(1L)" it works. Any idea what I am missing?
When you use multiple generators in a for comprehension, the monads have to be of the same type. E.g. you can't:
scala> for{ x <- Some("hi"); y <- List(1,2,3) } yield (x,y)
<console>:11: error: type mismatch;
found : List[(String, Int)]
required: Option[?]
for{ x <- Some("hi"); y <- List(1,2,3) } yield (x,y)
^
What you can do is convert one or the other to match the correct type. For the above example, that would be:
scala> for{ x <- Some("hi").toSeq; y <- List(1,2,3) } yield (x,y)
res2: Seq[(String, Int)] = List((hi,1), (hi,2), (hi,3))
In your particular case, one of your generators is a GenTraversableOnce, and the other is a Future. You can probably use Future.successful(theList) to get two futures. See the answer here for example:
Unable to use for comprehension to map over List within Future
Based on #Brian's answer arrived at a solution.. following worked (could not enter formatter block of code as comment - so adding as answer):
usersService.searchUser(pattern) flatMap { usrList =>
val q = for {
u <- usrList
} yield (usersService.getUserWithAddress(u.id))
val r = Future.sequence(q)
r map { ps =>
Ok(views.html.UserList(ps))
}
}
For comprehension accumulated the Futures and then the sequence was flattened and then mapped. Hope this is how it is done!
Note: also I had to change the signature of getUserWithAddress to X instead of Seq[X]

Play Framework / Dependent Future Composition

I am trying to do several dependent Slick/DB calls and then display the resulting data within a twirl template.
def show(slug: String) = Action.async { implicit rs =>
for {
f <- fooDAO.findBySlug(slug) // f is of type Option[foo]
fid <- f.flatMap(a => a.id.map(b => b)) // fid is of type Long
b <- barDAO.findByFooId(fid) // b is of type Seq[bar]
} yield {
f.map {
case Some(f) => Ok(views.html.foobar(f, b))
case _ => NotFound
}
}
}
I first need to get the "ID" to then be able to query other relevant data. The compiler is now producing this error:
play.sbt.PlayExceptions$CompilationException: Compilation error[type mismatch;
found : scala.concurrent.Future[Option[play.api.mvc.Result]]
required: Option[?]]
Any help would be greatly appreciated.
There is a fundamental flaw in your code, in that you're mixing in the same comprehension an Option and a Seq
A for-comprehension is expected to work on the same "container" type, which will be the resulting representation of the yield
e.g. if you combine several Options you get an Option, if you combine Seqs you get a Seq.
In this case you can overcome the problem by converting the Option (foo) to a Seq (which will be empty if the foo is None and have 1 element if not).
The end result would be
val results: Seq[(Foo, Bar)] =
for {
f <- fooDAO.findBySlug(slug).toSeq // f is of type Seq[Foo]
b <- barDAO.findByFooId(f.id) // b is of type Seq[Bar]
} yield (f, b)
But I guess this is not what you need. I suppose you want to get all Bars associated with the retrieved Foo, if any, and present it with your template. If no Foo is present for the slug, you want a NotFound.
We can do it like this
def show(slug: String) = Action.async { implicit rs =>
val f = fooDAO.findBySlug(slug) // f is of type Option[Foo]
f.fold(
NotFound,
foo => Ok(views.html.foobar(foo, barDAO.findByFooId(foo.id))
)
}
You can make it more explicit by defining a supporting method
def show(slug: String) = Action.async { implicit rs =>
def barsOf(f: Foo): Seq[Bar] = barDAO.findByFooId(f.id)
val f = fooDAO.findBySlug(slug) // f is of type Option[Foo]
f.fold(
NotFound,
foo => Ok(views.html.foobar(foo, barsOf(foo))
)
}
It's a bit tricky understanding what you're trying to achieve here, but if the whole thing is predicated on the findbySlug returning a Future[Option[Foo]] and the eventual outcome being a NotFound if that Option is a None, then your yield should probably just be:
...
} yield {
f.fold(NotFound)(foo => Ok(views.html.foobar(foo, b)))
}
Option[T] is a fantastic type for data-retrieval and control-flow, but pattern-matching on it is almost never the right approach. The use of fold feels nicely succinct for the task.

Why does a for comprehension expand to a `withFilter`

I'm working on a DSL for relational (SQL-like) operators. I have a Rep[Table] type with an .apply: ((Symbol, ...)) => Obj method that returns an object Obj which defines .flatMap: T1 => T2 and .map: T1 => T3 functions. As the type Rep[Table] does not know anything about the underlying table's schema, the apply method acts like a projection - projecting only the fields specified in the argument tuple (a lot like the untyped scalding api). Now type T1 is a "tuple-like", its length constrained to the projection tuple's length by some shapeless magic, but otherwise the types of the tuple elements are decided by the api user, so code like
val a = loadTable(...)
val res = a(('x, 'y)).map { (t: Row2[Int, Int]) =>
(1, t(0))
}
or
val res = a(('x, 'y)).map { (t: Row2[String, String]) =>
(1, t(0))
}
works fine. Note that the type of the argument for the map/flatMap function must be specified explicitly. However, when I try to use it with a for comprehension
val a = loadTable(...)
val b = loadTable(...)
val c = loadTable(...)
val res = for {
as: Row2[Int, Int] <- a(('ax, 'ay))
bs: Row2[Int, Int] <- b(('bx, 'by))
cs: Row2[Int, Int] <- c(('cx, 'cy))
} yield (as(0), as(1), bs(0), bs(1), cs(0), cs(1))
it complains about the lack of a withFilter operator. Adding an .withFilter: T1 => Boolean does not cut it - it then complains about "missing parameter type for expanded function", as T1 is parameterised by some type. Only adding .withFilter: Row[Int, Int] => Boolean makes it work, but obviously is not what I want at all.
My questions are: why does the withFilter get called in the first place and how can I use it with my parameterised tuple-like type T1?
Edit
In the end I went with a .withFilter: NothingLike => BoolLike which is a noop for simple checks like _.isInstanceOf[T1] and a more restricted .filter: T1 => BoolLike to be used in general case.
Unfortunately, you cannot use for-comprehensions when you expect type inference based on the argument type of your lambda.
Actually, in your example, as: Row2[Int, Int] is interpreted as a pattern match:
val res = for {
as: Row2[Int, Int] <- a(('ax, 'ay))
} yield (...)
Translates to something like:
a(('ax, 'ay)).withFilter(_.isInstanceOf[Row2[Int, Int]]).map(...)
Pattern matching in for comprehensions can be very useful:
val names = for {
("name", name) <- keyValPairs
} yield name
But the trade-off is, that you cannot explicitly specify the argument type of the lambda.
I bumped into this issue also. Thanks to gzm0 explaining the Scala compilers behaviour I came up with this workaround:
import cats._
import cats.data._
import cats.implicits._
object CatsNEL extends App {
val nss: NonEmptyList[(Int, String)] = NonEmptyList.of((1,"a"), (2, "b"), (3, "c"))
val ss: NonEmptyList[String] = for {
tuple <- nss
(n, s) = tuple
} yield s
}

Multiple Fields in For Expression

Given a List of JsObject's, I'd like to convert it to a Map[String, JsValue], and then iterate through each map's key-value pair, making a JsObject per pair. Finally, I'd like to put all of these JsObject's into a List[JsObject].
Below is my attempt with a for expression.
def getObjMap(obj: JsObject): Option[Map[String, JsValue]] = obj match {
case JsObject(fields) => Some(fields.toMap)
case _ => None
}
def createFields(x: (String, JsValue)): JsObject = Json.obj("n" -> x._1,
"v" -> x._2)
val objects: List[JsObject] = foo()
val res: List[JsObject] = for {
obj <- objects
map <- getObjMap(obj)
mapField <- map
} yield(createFields(mapField))
However, I'm getting a compile-time error on the mapField <- map line.
[error] ...\myApp\app\services\Test.scala:65: type mismatch;
[error] found : scala.collection.immutable.Iterable[play.api.libs.
json.JsObject]
[error] required: Option[?]
I think the problem is coming from the fact that the for comprehension starts by working over a List, then ends up working over an Option (from getObjMap). The type of construct needs to remain constant across the whole for comprehension. Calling .toList on the result of the getObjMap call fixes this:
val res: List[JsObject] = for {
obj <- objects
map <- getObjMap(obj).toList
mapField <- map
} yield(createFields(mapField))

This strange type [T*]

For example, I have following function that concatenates beginings and endings producing all possible variants of concatenation as result:
def mixer1(begin: String, beginings: String*)(end: String, endings: String*) =
for (b <- (begin +: beginings); e <- (end +: endings)) yield (b + e)
Actually what function does is not impotant, I want to rewrite it this way:
def mixer2(begin: String, beginings: String*):Function2[String, Seq[String], Seq[String]] = {
return new Function2[String, Seq[String], Seq[String]] {
def apply(end:String, endings:Seq[String]) = for(b <- (begin +: beginings); e <- (end +: endings)) yield b+e
}
}
Obviously, second one wouldn't work as expected because apply's second parameter has type Seq[String] but not String* (howewer they both compile to Seq[String]):
scala> mixer1("a","b")("c","d")
res0: Seq[java.lang.String] = ArrayBuffer(ac, ad, bc, bd)
scala> mixer2("a","b")("c","d")
<console>:10: error: type mismatch;
found : java.lang.String("d")
required: Seq[String]
mixer2("a","b")("c","d")
How can I (if I can) redefine mixer2 function?
Try this way:
def mixer2(begin: String, beginings: String*) = {
new ((String, String*) => Seq[String]) {
def apply(end: String, endings: String*) = for(b <- (begin +: beginings); e <- (end +: endings)) yield b+e
}
}
We use type inference on mixer2 to get the correct type. This means return must be removed, but that's ok, since it was unnecessary (and, generally advised against on Scala). The big trick is using the A => B syntactic sugar for Function to be able to use String*. Then just change apply as expected.
You can go the easy way:
def mixer2 = mixer1 _
As in your example, you can just do this
implicit def toSeq[T](x: T): Seq[T] = List(x)
But when it comes to more arguments, like
mixer2("a","b")("c","d","e")
There seems to be no suitable solution, although there has been an implicit conversion from Array[T] to Seq[T]. Because T* is a syntax candy, ("c","d","e") will be seen as 3 arguments. Compiler cannot recognize which ones form an array.
So I wonder what is your actual scenario, this example looks strange.
If you just want a part of the function, #Debilski's solution is a great way.
You can escape without even naming the intermediate return type.
def mixer2(begin: String, beginings: String*) = new {
def apply(end: String, endings: String*) =
for (b <- (begin +: beginings); e <- (end +: endings)) yield b+e
}
People will try to tell you this involves reflection. Surprisingly enough, it does not.