I have a variable obj: Option[MyObject] and want to extract multiple variables from it - if the object is not set, default values should be used.
Currently I do it like this:
val var1 = obj match {
case Some(o) => e.var1
case _ => "default1"
}
val var2 = obj match {
case Some(o) => e.var2
case _ => "default2"
}
...
which is extremely verbose. I know I could do it like this:
val var1 = if (obj.isDefined) obj.get.var1 else "default1"
val var2 = if (obj.isDefined) obj.get.var2 else "default2"
which still seems strange. I know I could use one big match and return a value object or tuple.
But what I would love is something similar to this:
val var1 = obj ? _.var1 : "default1"
val var2 = obj ? _.var2 : "default2"
Is this possible somehow?
How about this?
obj.map(_.var1).getOrElse("default1")
or, if you prefer this style:
obj map (_ var1) getOrElse "default"
Another variation would be to use a version of the Null Object Pattern and use the object directly
//could also be val or lazy val
def myDefault = new MyObject {
override val var1 = "default1"
override val var2 = "default2"
}
val myObj = obj getOrElse myDefault
use(myObj.var1)
use(myObj.var2)
To extract multiple values from an Option I'd recommend returning a tuple and using the extractor syntax:
val (val1, val2) = obj.map{o => (o.var1, o.var2)}.getOrElse(("default1", "default2"))
Related
The trivial approach (with if,else), is known.
I'm thinking about how Scala can help me to do it in a more elegant way:
def prepareData(baseObj: BaseObj): Option[NextObj] = {
val maybeDataOne = Option(baseObj.getDataOne)
val maybeDataTwo = Option(baseObj.getDataTwo)
// return None if no DataOne or DataTwo defined
// return Some(NextObj) if at least one of the Datas defined.
// trivial solution:
if(maybeDataOne.isDefined || maybeDataTwo.isDefined) {
Some(NextObj(
dataOne = baseObj.dataOne,
dataTwo = baseObj.dataTwo
))
} else None
}
//DataOne and DataTwo will be mapped to NextObj, if, at least one, is defined
case class NextObj(d1: Option[DataOne], d2: Option[DataTwo])
maybeDataOne orElse maybeDataTwo map { _ => nextObj }
One way to make this look even prettier is to equip your NextObject class with a .toOption method:
def toOption = d1 orElse d2 map { _ => this }
Then you can just write NextObject(maybeDataOe, maybeDataTwo).toOption at the call site.
Or maybe this:
object NextObject {
def opt(d1: Option[DataOne], d2: Option[DataTwo]) =
d1 orElse d2 map { _ => apply(d1, d2) }
}
and then just NextObject.opt(maybeDataOne, maybeDataTwo)
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.
Let there is a map config: Map[String, String].
And there are several keys: "foo", "bar", ...
I need to make sure that all keys are present in config. And if they are present, I need to call a function with values for these keys in the config map:
fun(config("foo"), config("bar"), config(...), ...)
The following is a solution:
val res = Option.when(config.contains("foo") & config.contains("bar") & config.contains(...) & ...)
( fun(config("foo"), config("bar"), config(...), ...) )
Or maybe:
val set = Set("foo", "bar", ...)
val res = Option.when(config.view.filterKeys(set).size == set.size)
( fun(config("foo"), config("bar"), config(...), ...) )
Both approaches look ugly and ineffective. Is there more concise way to implement the same behavior?
Since the set of keys is static, you do not need to do anything too complex.
You just need to attempt to get each key and if all are in call the function and wrap the result in a Some, if not then return a None.
for {
value1 <- config.get("key1")
// ...
valueN <- config.get("keyN")
} yield fun(value1, ..., valueN)
If you have cats, you can do it like.
(
config.get("key1"),
// ...
config.get("keyN")
).mapN(fun(_).tupled)
Consider forall in combination with contains
val requiredKeys = List("foo", "bar")
if (requiredKeys forall config.contains) {
// work with config
} else {
// handler error
}
or based on Tom Crockett consider keySet approach
val requiredKeys = Set("foo", "bar")
if (requiredKeys subsetOf config.keySet) {
// work with config
} else {
// handler error
}
if you have your keys as ("foo", "bar", ...).map(config) returns the values, right? then if it contains a None, then not all keys are found. I would start to think along this idea.
Passing elements of a List as parameters to a function with variable arguments helps here, so val args = list.map(config);
then the condition to check if all values are present, and finally
fun(args:_*).
how about that?
This is using cats, a very common FP library:
import cats.implicits._
def fun(a: String, b: String, c: String): MyConfigClass = ???
val parsedOpt: Option[MyConfigClass] =
(config.get("a"), config.get("b"), config.get("c"))
.mapN(fun)
The mapN method does what you want, it will extact all values if they exist and provide them to fun. For the curious, it relies on the fact that Option is an Applicative.
To show its power, you could also get back a list of missing keys, to know where the issue was:
import cats.data._
def getConfig(key: String): Either[NonEmptyList[String], String] =
config.get(key).toRightNel(s"Key $key not found")
val parsedValidated: Either[NonEmptyList[String], MyConfigClass] =
(getConfig("a"), getConfig("b"), getConfig("c"))
.parMapN(fun)
The following is a flatMap way:
config.get("key1").flatMap(key1 =>
config.get("key2").flatMap(key2 =>
...
config.get("keyN").flatMap(keyN =>
fun(key1, key2, ..., keyN)
))...)
I have created my scala map as :
val A:Map[String, String] = Map()
Then I am trying to add entries as :
val B = AttributeCodes.map { s =>
val attributeVal:String = <someString>
if (!attributeVal.isEmpty)
{
A + (s -> attributeVal)
}
else
()
}
And after this part of the code, I am seeing A is still empty. And, B is of type :
Pattern: B: IndexedSeq[Any]
I need a map to add entries and the same or different map in return to be used later in the code. However, I can not use "var" for that. Any insight on this problem and how to resolve this?
Scala uses immutability in many cases and encourages you to do the same.
Do not create an empty map, create a Map[String, String] with .map and .filter
val A = AttributeCodes.map { s =>
val attributeVal:String = <someString>
s -> attributeVal
}.toMap.filter(e => !e._1.isEmpty && !e._2.isEmpty)
In Scala, the default Map type is immutable. <Map> + <Tuple> creates a new map instance with the additional entry added.
There are 2 ways round this:
Use scala.collection.mutable.Map instead:
val A:immutable.Map[String, String] = immutable.Map()
AttributeCodes.forEach { s =>
val attributeVal:String = <someString>
if (!attributeVal.isEmpty){
A.put(s, attributeVal)
}
}
Create in immutable map using a fold:
val A: Map[String,String] = AttributeCodes.foldLeft(Map(), { m, s =>
val attributeVal:String = <someString>
if (!attributeVal.isEmpty){
m + (s -> attributeVal)
} else {
m
}
}
I'm filtering a list using this code :
linkVOList = linkVOList.filter(x => x.getOpen().>=(100))
The type x is inferred by Scala which is why it can find the .getOpen() method.
Can the code 'x => x.getOpen()' be extracted to a local variable ? something like :
val xval = 'x => x.getOpen()'
and then :
linkVOList = linkVOList.filter(xval.>=(100))
I think this is difficult because the .filter method infers the type wheras I need to work out the type outside of the .filter method. Perhaps this can be achieved using instaneof or an alternative method ?
There are a couple of ways to do what you are asking, but both ways will explicitly have to know the type of object they are working with:
case class VO(open:Int)
object ListTesting {
def main(args: Array[String]) {
val linkVOList = List(VO(200))
val filtered = linkVOList.filter(x => x.open.>=(100))
val filterFunc = (x:VO) => x.open.>=(100)
linkVOList.filter(filterFunc)
def filterFunc2(x:VO) = x.open.>=(100)
linkVOList.filter(filterFunc2)
}
}
Since you haven't provided any such information, I'll imply the following preconditions:
trait GetsOpen { def getOpen() : Int }
def linkVOList : List[GetsOpen]
Then you can extract the function like this:
val f = (x : GetsOpen) => x.getOpen()
or this:
val f : GetsOpen => Int = _.getOpen()
And use it like this:
linkVOList.filter( f.andThen(_ >= 100) )
Just use
import language.higherKinds
def inferMap[A,C[A],B](c: C[A])(f: A => B) = f
scala> val f = inferMap(List(Some("fish"),None))(_.isDefined)
f: Option[String] => Boolean = <function1>
Now, this is not the value but the function itself. If you want the values, just
val opened = linkVOList.map(x => x.open)
(linkVOList zip opened).filter(_._2 >= 100).map(_._1)
but if you want the function then
val xfunc = inferMap(linkVOList)(x => x.open)
but you have to use it like
linkVOList.filter(xfunc andThen { _ >= 100 })
or
linkVOList.filter(x => xfunc(x) >= 100)
since you don't actually have the values but a function to compute the values.