Multiple Fields in For Expression - scala

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))

Related

For comprehension with optional collection iteration and yield

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

How to do flatten in scala horizantally?

I am trying some basic logic using scala . I tried the below code but it throws error .
scala> val data = ("HI",List("HELLO","ARE"))
data: (String, List[String]) = (HI,List(HELLO, ARE))
scala> data.flatmap( elem => elem)
<console>:22: error: value flatmap is not a member of (String, List[String])
data.flatmap( elem => elem)
Expected Output :
(HI,HELLO,ARE)
Could some one help me to fix this issue?
You are trying to flatMap over a tuple, which won't work. The following will work:
val data = List(List("HI"),List("HELLO","ARE"))
val a = data.flatMap(x => x)
This will be very trivial in scala:
val data = ("HI",List("HELLO","ARE"))
println( data._1 :: data._2 )
what exact data structure are you working with?
If you are clear about you data structure:
type rec = (String, List[String])
val data : rec = ("HI",List("HELLO","ARE"))
val f = ( v: (String, List[String]) ) => v._1 :: v._2
f(data)
A couple of observations:
Currently there is no flatten method for tuples (unless you use shapeless).
flatMap cannot be directly applied to a list of elements which are a mix of elements and collections.
In your case, you can make element "HI" part of a List:
val data = List(List("HI"), List("HELLO","ARE"))
data.flatMap(identity)
Or, you can define a function to handle your mixed element types accordingly:
val data = List("HI", List("HELLO","ARE"))
def flatten(l: List[Any]): List[Any] = l.flatMap{
case x: List[_] => flatten(x)
case x => List(x)
}
flatten(data)
You are trying to flatMap on Tuple2 which is not available in current api
If you don't want to change your input, you can extract the values from Tuple2 and the extract the values for second tuple value as below
val data = ("HI",List("HELLO","ARE"))
val output = (data._1, data._2(0), data._2(1))
println(output)
If that's what you want:
val data = ("HI",List("HELLO,","ARE").mkString(""))
println(data)
>>(HI,HELLO,ARE)

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]

How to invoke map() from a ProductIterator (Tuple)

Given following placeholder logging method:
def testshow(value: Any) = value.toString
In the following code snippet:
case t : Product =>
t.productIterator.foreach( a => println(a.toString))
val lst = t.productIterator.map(a => testshow(a))
val lst2 = t.productIterator.map(_.toString)
lst.mkString("(",",",")")
lst2.mkString("(",",",")")
And given an input tuple :
(Some(OP(_)),Some(a),1)
The println successfully shows entries for the given tuple.
Some(OP(_))
Some(a)
1
lst2 (with toString) says: Non-empty iterator. However the list "lst" says:
empty iterator
So what is wrong with the syntax to invoke the map() method on the productIterator?
Note: if putting "toString" in place of testshow this works properly.
Update: A "self contained" snippet does work. It is still not clear why the above code does not..
def testshow(value: Any) = "TestShow%s".format(value.toString)
val obj = ("abc",123,"def")
obj match {
case t : Product =>
t.productIterator.foreach( a => println(a.toString))
val lst = t.productIterator.map(a => testshow(a))
val lst2 = t.productIterator.map(_.toString)
println("testshow list %s".format(lst.mkString("(",",",")")))
println("toString list %s".format(lst2.mkString("(",",",")")))
}
Output:
abc
123
def
testshow list (**abc**,**123**,**def**)
toString list (abc,123,def)
Iterators can be traversed only once, then they are exhausted. Mapping an iterator produces another iterator. If you see your iterator empty, you must have forced a traversal.
scala> case class Foo(a: Int, b: Int)
defined class Foo
scala> Foo(1, 2).productIterator.map(_.toString)
res1: Iterator[String] = non-empty iterator
It is non-empty. Are you sure you used a fresh iterator? Because if you used the same iterator for the first foreach loop, then it would be empty if you tried to map the same iterator afterwards.
Edit: The shape of the map function argument has nothing to do with this:
def testshow(value: Any) = value.toString
case class OP(x: Any)
def test(x: Any) = x match {
case t: Product =>
val lst = t.productIterator.map(a => testshow(a))
lst.mkString("(", ",", ")")
case _ => "???"
}
test((Some(OP(_)),Some('a'),1)) // "(Some(<function1>),Some(a),1)"
Looks like an Intellij Bug. I just changed the name of the "lst" variable to "lst3" and it works. I repeated the process of name/rename back/forth and it is repeatable bug. There are no other occurrences of "lst" in the entire file and in any case it was a local variable.

Append Map[String, String] to a Seq[Map[String, String]]

This drives me crazy, I can't figure out why this gives me an error.
Here an example of my code:
var seqOfObjects:Seq[Map[String, String]] = Seq[Map[String, String]]()
for(item <- somelist) {
seqOfObjects += Map(
"objectid" -> item(0).toString,
"category" -> item(1),
"name" -> item(2),
"url" -> item(3),
"owneremail" -> item(4),
"number" -> item(5).toString)
}
This gives me an error saying:
Type mismatch, expected: String, actual: Map[String, String]
But a Map[String, String] is exactly what I want to append into my Seq[Map[String, String]].
Why is it saying that my variable seqOfObjects expects a String??
Anyone have a clue?
Thanks
a += b means a = a.+(b). See this answer.
There is no method + in Seq, so you can't use +=.
scala> Seq[Int]() + 1
<console>:8: error: type mismatch;
found : Int(1)
required: String
Seq[Int]() + 1
^
required: String is from string concatenation. This behavior is inherited from Java:
scala> List(1, 2, 3) + "str"
res0: String = List(1, 2, 3)str
Actually method + here is from StringAdd wrapper. See implicit method Predef.any2stringadd.
You could use :+= or +:= instead of +=.
Default implementation of Seq is List, so you should use +: and +:= instead of :+ and :+=. See Performance Characteristics of scala collections.
You could also use List instead of Seq. There is :: method in List, so you can use ::=:
var listOfInts = List[Int]()
listOfInts ::= 1
You can rewrite your code without mutable variables using map:
val seqOfObjects =
for(item <- somelist) // somelist.reverse to reverse order
yield Map(...)
To reverse elements order you could use reverse method.
Short foldLeft example:
sl.foldLeft(Seq[Map[Srting, String]]()){ (acc, item) => Map(/* map from item */) +: acc }