Scala proper way to initialize ones changed in runtime fields: placeholder/null, None or zero element? - scala

I got class with fields which value at initialization is unknown. But after, in runtime that values is will gained and setted to fields just ones.
I want to decide what first initialization is best to use for that fields. As i read, there are such methods:
using placeholder _ or null [Bad way]:
var name: String = _
var nextUser: User = null
using None, and than in my code Some(v)[Good, but verbose]:
var name: Option[String] = None
var nextUser: Option[User] = None
using "zero" element:
var name: String = ""
var nextUser: User = new User()
using stub:
var name: String = "undefined"
var nextUser: User = UndefinedUser
I see 3 problems:
it is verbose to get values from Some() writing any times .get or using match/case
it is bad to use var for field which really will be setted by value just one time, but in runtime
it is bad to write updateUser-like methods
Now i am using None in that fields, because for some types, which is not in my library, is no any constructor or empty\"zero" value:
class ClassWithLazyFields {
var name: String = ""
var age: Int = 0
//here, after first asignment as `None` i will set it values only one time
var myThread: Option[Thread] = None
var nextUser: Option[User] = None
var myTransformUnit: Option[TransformUnit] = None
def updateUser(u: User): Unit = {
nextUser = u
}
}
//after first asignment as `None` i set nextUser value like that
classInstance.updateUser(userCat)
// bad ".get" in callings
val name = classInstance.myThread.get.name
val hoursToStart = classInstance.myTransformUnit.get.waitTime.hours
// or more verbose match/case
val hoursToStart = classInstance.myTransformUnit match {
case Some(v) => v.waitTime.hours
case None => 0
}
What you can advice to me?
I need something like lazy var or any good advice.

The advice is to avoid using mutable data structures like this in the first place.
Make the class immutable and change methods like updateUser to return a new updated instance rather than modifying the current instance.
But if you must do this, Option is specifically designed for cases where values may or may not be present. Methods like map and getOrElse make it easy (and safe) to use Option values with very little overhead.
For example, this is how you safely calculate name and hoursToStart:
val name = classInstance.myThread.fold("NoName")(_.name)
val hoursToStart = classInstance.myTransformUnit.fold(0)(_.waitTime.hours)
If you want to use multiple Option values, use for like this:
for {
thread <- classInstance.myThread
user <- classInstance.nextUser
unit <- classInstance.myTransformUnit
} {
// Code that uses thread, user, and unit
}
The code will only be called if all three values are not None.

Related

while comparing the two objects in scala not getting correct result

Pojo classes
case class RulePojoMigration(val tolerance_id:Int,val asset_id:Int, measure: String, cond: String, function: String, threshold_value: String, rule_dts: String,
filter: util.List[Filter], is_enabled: String, mode: String, tolerance_status: String, action_key: String,
email_ids: util.List[String], rule_name:String, rule_owner:String, group_by: util.List[String], relax_variation_band:String, reconciliation_id:Int)
case class Filter(value: String, operator: String, dimension: String)
Below is the code used to fetch the object from DB
if (checkRuleIsEditOrNot) {
ps = con.prepareStatement(WSQueryConstant.RULE_MIGRATION_GET_COMPLETE_RULE_INFO_FROM_STAGE)
ps.setInt(1, rulePojo.tolerance_id)
rs = ps.executeQuery
var dbRulePojo: RulePojoMigration = null
val jsonRulePojo: RulePojoMigration = rulePojo
if (rs.next()) {
val rule_tolerance_asset_id = rs.getInt(2)
val measure = rs.getString(3)
val cond = rs.getString(4)
val function = rs.getString(5)
val threshold_value = rs.getString(6)
val rule_dts = rs.getString(7)
val filter = gson.fromJson(rs.getString(8), classOf[util.List[Filter]])
val is_enabled = rs.getString(9)
val mode = rs.getString(10)
val tolerance_status = rs.getString(11)
val email_ids = gson.fromJson(rs.getString(12), classOf[java.util.List[String]])
val rule_name = rs.getString(13)
val rule_owner = rs.getString(14)
val group_by = gson.fromJson(rs.getString(15), classOf[java.util.List[String]])
val relax_variation_band = rs.getString(16)
var reconciliation_id = rs.getString(17)
if(reconciliation_id== null)
reconciliation_id= "-1"
dbRulePojo = RulePojoMigration(jsonRulePojo.tolerance_id, rule_tolerance_asset_id, measure, cond, function, threshold_value, rule_dts, filter, is_enabled, mode, tolerance_status, jsonRulePojo.action_key, email_ids, rule_name, rule_owner, group_by, relax_variation_band, reconciliation_id.toInt)
user request with the below values.
RulePojoMigration(1274,1234,*,less than,count,100,2020-07-04 05:31:29,[Filter(Hello,equal,funnel_state)],1,static,null,EMAIL,[x],StaticRuleMigrationTesting,email,[ALL],0,-1)
Object available in MySQL database
RulePojoMigration(1274,1234,*,less than,count,100,2020-07-04 05:31:29,[{value=Hello, operator=equal, dimension=funnel_state}],1,static,null,EMAIL,[x],StaticRuleMigrationTesting,email,[ALL],0,-1)
So I want to check the equality of two objects so that the user can modify the the existing object if there is any changes in the payload or simply he/she should get a message saying duplicate entry exist. jsonRulePojo.equal(dbRulePojo) should be true, anyhow values are same of both object.
The default equals operation does not check the contents of the two classes, it just checks whether both values refer to the same object. So two instances of RulePojoMigration will always be different even if every field is the same. By default two classes are only equal if they are the same instance.
When a case class is defined, Scala will generate an equals that does check each of the fields passed to the constructor in turn. So two case classes will be equal if they have the same contents.
So you can fix this code in one of two ways:
Make RulePojoMigration a case class
Add an equals method to RulePojoMigration
I am able to resolve the bug by making the below changes.
From
val filter = gson.fromJson(rs.getString(8), classOf[util.List[Filter]])
To
val filter = gson.fromJson(rs.getString(8), classOf[Array[Filter]])
and
dbRulePojo = RulePojoMigration(jsonRulePojo.tolerance_id, rule_tolerance_asset_id, measure, cond, function, threshold_value, rule_dts, java.util.Arrays.asList(filter:_*)
, is_enabled, mode, tolerance_status, jsonRulePojo.action_key, email_ids, rule_name, rule_owner, group_by, relax_variation_band, reconciliation_id.toInt)
And instead of passing the filter directly into dbRulePojo i passed the java.util.Arrays.asList(filter:_*)

Scala : How to pass a class field into a method

I'm new to Scala and attempting to do some data analysis.
I have a CSV files with a few headers - lets say item no., item type, month, items sold.
I have made an Item class with the fields of the headers.
I split the CSV into a list with each iteration of the list being a row of the CSV file being represented by the Item class.
I am attempting to make a method that will create maps based off of the parameter I send in. For example if I want to group the items sold by month, or by item type. However I am struggling to send the Item.field into a method.
F.e what I am attempting is something like:
makemaps(Item.month);
makemaps(Item.itemtype);
def makemaps(Item.field):
if (item.field==Item.month){}
else (if item.field==Item.itemType){}
However my logic for this appears to be wrong. Any ideas?
def makeMap[T](items: Iterable[Item])(extractKey: Item => T): Map[T, Iterable[Item]] =
items.groupBy(extractKey)
So given this example Item class:
case class Item(month: String, itemType: String, quantity: Int, description: String)
You could have (I believe the type ascriptions are mandatory):
val byMonth = makeMap[String](items)(_.month)
val byType = makeMap[String](items)(_.itemType)
val byQuantity = makeMap[Int](items)(_.quantity)
val byDescription = makeMap[String](items)(_.description)
Note that _.month, for instance, creates a function taking an Item which results in the String contained in the month field (simplifying a little).
You could, if so inclined, save the functions used for extracting keys in the companion object:
object Item {
val month: Item => String = _.month
val itemType: Item => String = _.itemType
val quantity: Item => Int = _.quantity
val description: Item => String = _.description
// Allows us to determine if using a predefined extractor or using an ad hoc one
val extractors: Set[Item => Any] = Set(month, itemType, quantity, description)
}
Then you can pass those around like so:
val byMonth = makeMap[String](items)(Item.month)
The only real change semantically is that you explicitly avoid possible extra construction of lambdas at runtime, at the cost of having the lambdas stick around in memory the whole time. A fringe benefit is that you might be able to cache the maps by extractor if you're sure that the source Items never change: for lambdas, equality is reference equality. This might be particularly useful if you have some class representing the collection of Items as opposed to just using a standard collection, like so:
object Items {
def makeMap[T](items: Iterable[Item])(extractKey: Item => T): Map[T,
Iterable[Item]] =
items.groupBy(extractKey)
}
class Items(val underlying: immutable.Seq[Item]) {
def makeMap[T](extractKey: Item => T): Map[T, Iterable[Item]] =
if (Item.extractors.contains(extractKey)) {
if (extractKey == Item.month) groupedByMonth.asInstanceOf[Map[T, Iterable[Item]]]
else if (extractKey == Item.itemType) groupedByItemType.asInstanceOf[Map[T, Iterable[Item]]]
else if (extractKey == Item.quantity) groupedByQuantity.asInstanceOf[Map[T, Iterable[Item]]]
else if (extractKey == Item.description) groupedByDescription.asInstanceOf[Map[T, Iterable[Item]]]
else throw new AssertionError("Shouldn't happen!")
} else {
Items.makeMap(underlying)(extractKey)
}
lazy val groupedByMonth = Items.makeMap[String](underlying)(Item.month)
lazy val groupedByItemType = Items.makeMap[String](underlying)(Item.itemType)
lazy val groupedByQuantity = Items.makeMap[Int](underlying)(Item.quantity)
lazy val groupedByDescription = Items.makeMap[String](underlying)(Item.description)
}
(that is almost certainly a personal record for asInstanceOfs in a small block of code... I'm not sure if I should be proud or ashamed of this snippet)

How to avoid var's which get assigned in loops

I'm iterating over the command line input arguments in my Scala program and want to set several variables in a loop. My problem is that I'm using var's.
I don't want to use big libraries for this task, I want to keep it simple. The arguments contain simple key value pairs (like "-v myVar"), that's why I try to use the sliding function. Probably not the best choice for setting val's.
object Main {
def main(args: Array[String]): Unit = {
// args: Array("-c", "myFile", "-j", "update", "-d", "debugPath")
var config: String = null
var jobType: String = null
var debug: String = null
args.sliding(2,2).toList.collect {
case Array("-c", argProperty: String) => config = argProperty
case Array("-j", argJobType: String) => jobType = argJobType
case Array("-d", argDebug: String) => debug = argDebug
}
println("Config: " + config)
println("Job type: " + jobType)
println("Debug: " + debug)
}
}
The code compiles and delivers the correct output, but the 3 var's are not so nice. However, I could not find a solution for declaring 3 val's in a loop which are used outside the loop.
This code turns your arg list in to a Map from argument name to value:
val argMap = args.grouped(2).collect{ case Array(k, v) => k -> v }.toMap
val config = argMap.getOrElse("-c", defaultConfig)
This avoids using null and allows you to easily tell whether a parameter was supplied or not.

How to use scala class member name as variable

In scala, is there a way to access a class member,but the member name is a var?
Below is a code snippet, there is a class member called "one_star". I have a var whose value is "one_star", and I want use this var as the member name of the "case class".
case class Features(
// star about
var one_star: String = "0",
var two_star: String = "0",
var three_star: String = "0",
var four_star: String = "0",
var five_star: String = "0"
// other about
)
object Features {
def apply(): Features = {
val features = new Features()
var testVar = "one_star"
features.${testVar} = "1"
features
}
}
If you want to change field name dynamically, i.e. provide class variable name as value, find field that match given variable name and finally change the value for that field, there are several ways: the simple one is to use pattern match to check the field value and change instance value by yourself and return instance. However, it can be quite messy as you need to handle for every fields defined in your class and in case you have many fields, it can be quite cumbersome. Therefore, you will need some generic way to solve this problem.
Another approach is to use scala reflection. Scala reflection is designed for this, i.e. modifying your codes at runtime like your case and in more generic way. Following is a code snippet that change value of your instance for given field name.
case class Features(
// star about
var one_star: String = "0",
var two_star: String = "0",
var three_star: String = "0",
var four_star: String = "0"
// other about
) {
def copyInstance(variableName: String, value: String): Features = {
// 1. Initialize Features instance
val instance = Features()
// 2. Import scala reflection api.
import scala.reflect.runtime.{universe => ru}
// 3. Get the mirror of instance of Features class.
// Mirror will reflect to instance of Features case class, and we will use this instance mirror to modify its fields value.
val instanceMirror = ru.runtimeMirror(instance.getClass.getClassLoader).reflect(instance)
// 4. Now, Get the field whose value need to be changed - i.e. name you provide - variableName.
val field = ru.typeOf[Features].decl(ru.TermName(variableName)).asTerm
// 5. Get the mirror for that field so that we can read and write to this field.
val fieldMirror = instanceMirror.reflectField(field)
// 6. Finally, set the value to this field.
fieldMirror.set(value)
// 7. Return the changed instance.
instance
}
}
val features = Features()
val changedFeatures = features.copyInstance("one_star", "changed")
println(features)
println(changedFeatures)
//Result
Features(0,0,0,0)
Features(changed,0,0,0)
Also, note that you may need to handle the Exception for cases where invalid variable name and value is provided. In addition, if your case class contains >22 field parameters, certain features of case class are not available.
Scala is static type language and doesn't allow these language constructions. But you can use reflection (hard way) or pattern matching with code like this one (simple way):
class Features (
var one_star: String = "0",
var two_star: String = "0",
var three_star: String = "0",
var four_star: String = "0",
var five_star: String = "0") {
def setField(field: String, value: String): Unit = {
field match {
case "one_star" => one_star = value
case "two_star" => two_star = value
case "three_star" => three_star = value
case "four_star" => four_star = value
case "five_star" => five_star = value
}
}
}
This is possible using scala-reflect, although under most circumstances I would not recommend it.
import scala.reflect.runtime.universe._
val field = typeOf[Features].decl(TermName(testVar)).asTerm.accessed.asTerm
val mirror = runtimeMirror(classOf[Features].getClassLoader)
mirror.reflect(features).reflectField(field).set("1")
Are you sure you don't want to use or extend Map[String, String] for your class? So many properties is not typical.

Scala Lists and Option

I must be doing something wrong. I come form a Java background so this stuff should be easy.
I'm wanting to create a mapping between a key and multiple values held in a list:
var keys = Map[String, ListBuffer[String]]()
However, I can't seem to add a value to the list!!! What am I doing wrong??
def put(key: String, value: String) = {
var valueOption = keys.get(key)
var values = valueOption.getOrElse(ListBuffer)
values += value
// value not added
}
I do not want to use a MultiMap because I need to do some other operations which are not easy to do with a MultiMap.
Please help.
Thanks
The other answers are right about how you're not putting the new ListBuffer back in the Map, but their example code is verbose. A mutable Map has getOrElse and getOrElseUpdate methods for this. Also, use val not var for locals and the keys member, unless you have reason not to. I sometimes prefer append to +=.
def put(key: String, value: String) = {
keys.getOrElseUpdate(key, ListBuffer()) += value
}
The problem is here:
var valueOption = keys.get(key)
var values = valueOption.getOrElse(ListBuffer)
For any nonexistent key, keys.get will return a None Option. You then call getOrElse, and since the "else" part is used (because it's a None), a new ListBuffer is initialized. However, this is all that happens.
In particular, the new ListBuffer is NOT automatically put into the map. Such an operation wouldn't make sense - getOrElse is part of the Option API, it cannot "know" about any collection the Option is generated from.
To correct your problem, you have to put the new ListBuffer into the map yourself. An example if you're using a mutable Map:
def put(key: String, value: String) = {
var valueOption = keys.get(key)
var values = valueOption.getOrElse {val b = ListBuffer.empty[String]; keys.put(key,b); b;}
values += value
}
the problem is, that by calling getOrElse(ListBuffer) you do not insert the new ListBuffer into the Map. So you need to add an additional step:
def put(key: String, value: String) = {
var valueOption =
var values = keys.get(key) match {
case None => // key not yet defined
buffer = ListBuffer()
// insert into map!
keys += key -> buffer
buffer
case Some(buffer) => buffer // key is already defined just return it
}
values += value
}
Note that for keys += key -> buffer to work, i assume, that you use an mutable Map (import from scala.collection.mutable.Map) instad of the default immutable Map
getOrElse will return the default ListBuffer, which is an empty ListBuffer, if key doesn't exist. You will need to associate this with you key.