How to properly map a tuple function across a Seq[A]? - scala

I have a function that is attempting to build a JSON object containing a representation of multiple tuples that are stored in a Seq[A], where A is a (Loop, Option[User]). My code looks like so:
def loops = Action {
val page : Int = 1
val orderBy : Int = 1
val filter : String = ""
val jsonifyLoops : (Loop, Option[User]) => Map[String, String] = {
case (loop,user) =>
Map(
"name" -> loop.name,
"created_at" -> loop.createdAt.map(dateFormat.format).getOrElse(""),
"deleted_at" -> loop.deletedAt.map(dateFormat.format).getOrElse(""),
"user_name" -> user.map(_.name).getOrElse("")
)
}
Ok(toJson(Map(
"loops" -> toJson(
Loop.list( page = page, orderBy = orderBy, filter = ("%"+filter+"%") )
.items.map( jsonifyLoops )
)
)
))
}
Loops.list produces a Page[A], from the helper class below:
case class Page[A](items: Seq[A], page: Int, offset: Long, total: Long) {
lazy val prev = Option(page - 1).filter(_ >= 0)
lazy val next = Option(page + 1).filter(_ => (offset + items.size) < total)
}
Thus, Loops.list(...).items should get me a Seq[(Loop, Option[User])], onto which I should be able to apply a map function. I've defined my jsonifyLoops function to have what I think is the appropriate prototype, but I must be doing something wrong, because the compiler throws me the following error:
[error] [...] Application.scala:42: type mismatch;
[error] found : (models.Loop, Option[models.User]) => Map[String,String]
[error] required: (models.Loop, Option[models.User]) => ?
[error] .items.map( jsonifyLoops )
[error] ^
What am I doing wrong?

Your function jsonifyLoops takes two arguments: a Loop and an Option[User]. However, the members of items are tuples of the type (Loop, Option[User]), and thus items.map requires as an argument a function of one argument accepting that tuple. So, you need to convert jsonifyLoops from a binary function to a unary function that takes a pair of arguments; Function2#tupled will do this for you:
scala> :t jsonifyLoops
(Loop, Option[User]) => Map[String,String]
scala> :t jsonifyLoops.tupled
((Loop, Option[User])) => Map[String,String]
You'd use it like this:
Loop.list(page = page, orderBy = orderBy, filter = ("%"+filter+"%"))
.items.map(jsonifyLoops.tupled)

You need to add a default case to your pattern matching within jasonifyLoops.
In absence of a default case, if your case statement fails you return a Unit.
So something like this should work:
val jsonifyLoops : (Loop, Option[User]) => Map[String, String] = {
case (loop,user) =>
Map(
"name" -> loop.name,
"created_at" -> loop.createdAt.map(dateFormat.format).getOrElse(""),
"deleted_at" -> loop.deletedAt.map(dateFormat.format).getOrElse(""),
"user_name" -> user.map(_.name).getOrElse("")
)
case _ => Map[String, String]()
}
This just says that if the input does not match, return an empty Map. However, you should replace this with whatever handling you want to do for the default case.

Related

Read Hocon config as a Map[String, String] with key in dot notation and value

I have following HOCON config:
a {
b.c.d = "val1"
d.f.g = "val2"
}
HOCON represents paths "b.c.d" and "d.f.g" as objects. So, I would like to have a reader, which reads these configs as Map[String, String], ex:
Map("b.c.d" -> "val1", "d.f.g" -> "val2")
I've created a reader and trying to do it recursively:
import scala.collection.mutable.{Map => MutableMap}
private implicit val mapReader: ConfigReader[Map[String, String]] = ConfigReader.fromCursor(cur => {
def concat(prefix: String, key: String): String = if (prefix.nonEmpty) s"$prefix.$key" else key
def toMap(): Map[String, String] = {
val acc = MutableMap[String, String]()
def go(
cur: ConfigCursor,
prefix: String = EMPTY,
acc: MutableMap[String, String]
): Result[Map[String, Object]] = {
cur.fluent.mapObject { obj =>
obj.value.valueType() match {
case ConfigValueType.OBJECT => go(obj, concat(prefix, obj.pathElems.head), acc)
case ConfigValueType.STRING =>
acc += (concat(prefix, obj.pathElems.head) -> obj.asString.right.getOrElse(EMPTY))
}
obj.asRight
}
}
go(cur, acc = acc)
acc.toMap
}
toMap().asRight
})
It gives me the correct result but is there a way to avoid MutableMap here?
P.S. Also, I would like to keep implementation by "pureconfig" reader.
The solution given by Ivan Stanislavciuc isn't ideal. If the parsed config object contains values other than strings or objects, you don't get an error message (as you would expect) but instead some very strange output. For instance, if you parse a typesafe config document like this
"a":[1]
The resulting value will look like this:
Map(a -> [
# String: 1
1
])
And even if the input only contains objects and strings, it doesn't work correctly, because it erroneously adds double quotes around all the string values.
So I gave this a shot myself and came up with a recursive solution that reports an error for things like lists or null and doesn't add quotes that shouldn't be there.
implicit val reader: ConfigReader[Map[String, String]] = {
implicit val r: ConfigReader[String => Map[String, String]] =
ConfigReader[String]
.map(v => (prefix: String) => Map(prefix -> v))
.orElse { reader.map { v =>
(prefix: String) => v.map { case (k, v2) => s"$prefix.$k" -> v2 }
}}
ConfigReader[Map[String, String => Map[String, String]]].map {
_.flatMap { case (prefix, v) => v(prefix) }
}
}
Note that my solution doesn't mention ConfigValue or ConfigReader.Result at all. It only takes existing ConfigReader objects and combines them with combinators like map and orElse. This is, generally speaking, the best way to write ConfigReaders: don't start from scratch with methods like ConfigReader.fromFunction, use existing readers and combine them.
It seems a bit surprising at first that the above code works at all, because I'm using reader within its own definition. But it works because the orElse method takes its argument by name and not by value.
You can do the same without using recursion. Use method entrySet as following
import scala.jdk.CollectionConverters._
val hocon =
"""
|a {
| b.c.d = "val1"
| d.f.g = val2
|}""".stripMargin
val config = ConfigFactory.load(ConfigFactory.parseString(hocon))
val innerConfig = config.getConfig("a")
val map = innerConfig
.entrySet()
.asScala
.map { entry =>
entry.getKey -> entry.getValue.render()
}
.toMap
println(map)
Produces
Map(b.c.d -> "val1", d.f.g -> "val2")
With given knowledge, it's possible to define a pureconfig.ConfigReader that reads Map[String, String] as following
implicit val reader: ConfigReader[Map[String, String]] = ConfigReader.fromFunction {
case co: ConfigObject =>
Right(
co.toConfig
.entrySet()
.asScala
.map { entry =>
entry.getKey -> entry.getValue.render()
}
.toMap
)
case value =>
//Handle error case
Left(
ConfigReaderFailures(
ThrowableFailure(
new RuntimeException("cannot be mapped to map of string -> string"),
Option(value.origin())
)
)
)
}
I did not want to write custom readers to get a mapping of key value pairs. I instead changed my internal data type from a map to list of pairs (I am using kotlin), and then I can easily change that to a map at some later internal stage if I need to. My HOCON was then able to look like this.
additionalProperties = [
{first = "sasl.mechanism", second = "PLAIN"},
{first = "security.protocol", second = "SASL_SSL"},
]
additionalProducerProperties = [
{first = "acks", second = "all"},
]
Not the best for humans... but I prefer it to having to build custom parsing components.

Why the type annotation is changing in Scala for one val

Why i am getting this type annotation difference in these below scenarios.
For scenario 1
case class TestData(name : String , idNumber : Int)
val createRandomData : immutable.IndexedSeq[Int => TestData]= (0 to 2).map{
_ => TestData("something",_)
}
For scenario 2
case class TestData(name : String , idNumber : Int)
val createRandomData: immutable.Seq[TestData] = (0 to 2).map{
i => TestData("something",i)
}
Why in scenario 1 is return type is a function not a collection of Seq.
When you do something like this:
case class TestData(name : String , idNumber : Int)
val createRandomData : immutable.IndexedSeq[Int => TestData]= (0 to 2).map{
_ => TestData("something",_)
}
the first underscore means that you ignore the value of the parameter, then you use another underscore in the body of the function passed to map so you are creation a lambda function that ends to be the return type.
What you wanted to in in first scenario was:
case class TestData(name : String , idNumber : Int)
val createRandomData = (0 to 2).map{
TestData("something",_)
}
Which has TestData as return type.
Because TestData("something",i) has type TestData and TestData("something",_) has type Int => TestData.
The second underscore is used for lambda (while the first underscore means that argument doesn't matter).
What are all the uses of an underscore in Scala?

Can I call a method inside of sortWith when sorting a sequence (or a map)?

I have a map Map[String,Option[Seq[String]]] and I have values for each of the string in a different map: Map[String,Option[Int]]. I am trying to map over the values and use a sortWith on the sequence but as I read online, I don't see any examples of having custom methods inside the sortWith.
How can I sort my sequence using sortWith? If I wanted to implement a custom method that returns a boolean to tell me what object is considered greater, is this possible?
val fieldMap = Map("user1" -> Seq("field1_name", "field2_name"), "user2" -> Seq("field3_name"))
val fieldValues = Map("field1_name" -> 2, "field2_name" -> 1, "field3_name" -> 3)
val sortedMap = fieldMap.mapValues(fieldList => fieldList.sortWith(fieldValues(_) < fieldValues(_)) // Scala doesn't like this
I tried:
fieldList.sortWith{(x,y) =>
val x = fieldValues(x)
val y = fieldValues(y)
x < y
}
This gives me a Type mismatch of expected type:
(String,String) => Boolean
and actual:
(String,String) => Any
EDIT Solution:
fieldList.sortWith{(x,y) =>
val x = fieldValues(x)
val y = fieldValues(x)
x.getOrElse[Double](0.0) < y.getOrElse[Double](0.0) // have to unwrap the Option.
}
You're using wrong syntax. For using sortWith you have to do something like:
fieldMap.mapValues(
fieldList => fieldList.sortWith(
(a,b) => fieldValues(a) > fieldValues(b)
)
)

Treat scala function with default parameter type as if it did not have default parameters

Disclaimer: I'm new to Scala.
I want to pass a function with default parameters as if its type did not have default parameters
import scala.util.hashing.MurmurHash3
type Record = Map[String, String]
type Dataset = Seq[Record]
def dropDuplicates(dataset: Dataset, keyF: Record => Any = recordHash) : Dataset = {
// keep only distinct records as defined by the key function
// removed method body for simplicity
return dataset
}
def recordHash(record: Record, attributes: Option[Seq[String]] = None) : Int = {
val values : Seq[String] = attributes
.getOrElse(record.keys.toSeq.sorted)
.map(attr => record(attr))
return MurmurHash3.seqHash(values)
}
Here's the error I'm getting at compile time:
error: type mismatch;
[ant:scalac] found : (Record, Option[Seq[String]]) => Int
[ant:scalac] required: Record => Any
[ant:scalac] def dropDuplicates(dataset: Dataset, keyF: Record => Any = recordHash) : Dataset = {
Intuitively, I think of recordHash as type Record => Int when the default parameter attributes is not provided. Is there a way to treat recordHash as type Record => Int?
I can't compile your code because I miss some types, but I think this will work.
def dropDuplicates(dataset: Dataset, keyF: Record => Any = recordHash(_)) : Dataset = {
// keep only distinct records as defined by the key function
}
This works because recordHash(_) is equivalent to x => recordHash(x), this way x (the input of the function) is Record which is the type you wanted.

Scala, Lift | SHtml.select - how to put list from database

I'm trying to put into form ( select ) some values from database:
val kateg = Kategoria.findAll.map(a => (a.id.toString , a.nazwa))
And next in form:
bind("entry", xhtml,
"kateg" -> SHtml.select(kateg, Empty, select ),
"temat" -> SHtml.text(temat, temat = _),
"opis" -> SHtml.textarea(opis, opis = _, "cols" -> "80", "rows" -> "8"),
"submit" -> SHtml.submit("Add", processEntryAdd))
And then i have error:
Description Resource Path Location Type
type mismatch; found : List[(java.lang.String, a.nazwa.type) for
Some { val a: code.model.Kategoria }]
required: Seq[(String, String)] Forma.scala
/lift-todo-mongo/src/main/scala/code/snippet
line 51 Scala Problem
any ideas ? Thanks
SHtml.select(..) allows you to choose a String value.
It takes a Seq of tuples (Value: String, Key: String)
In that case you probably need to write:
val kateg = Kategoria.findAll.map(a => (a.id.toString , a.nazwa.is))
if nazwa is MappedString field of Kategoria entity.
i.e. kateg should have a type of Seq[(String, String)]
But I would suggest you to use SHtml.selectObj to select Kategoria entity instead of String name value:
val kateg: Seq[(Kategoria, String)] = Kategoria.findAll.map(a => (a, a.nazwa.is))
SHtml.selectObj[Kategoria](kateg, Empty, (k: Kategoria) => { .. /* assign */ .. })