I've got a Scala def that takes parameters from an HTTP POST and parses the data. I'm pulling a "job" object from the database (the query was successful as verified in the debugger, and parameters are just as they need to be) and I'm trying to update that job object with the new parameters. However, trying to assign values are proving useless since the job object retains all original values.
All database objects are from Squeryl. Code below:
Edit: added class below and Job object to help give context in this Play! app
object Job {
def updateFromParams(params:Params) = {
val job = Job.get( params.get("job_id").toLong ).get
val comments = params.get("comments")
val startTime = parseDateTime(params.get("start_time") + " " + params.get("date"))
val endTime = parseDateTime(params.get("end_time") + " " + params.get("date"))
val clientId = params.get("client_id").toLong
val client = Client.get(clientId).get
val name = params.get("job_name")
val startAddressType = params.get("start_address_type")
var startLocationId:Option[Long] = None
val (startAddress, startCity, startProvince) = startAddressType match {
case "client" => getClientAddress(clientId)
case "custom" => (params.get("start_custom_address"),
params.get("start_custom_city"),
params.get("start_custom_province"))
case id => {
startLocationId = Some(id.toLong)
getLocationAddress(startLocationId.get)
}
}
job.comments -> comments
job.startTime -> startTime
job.endTime -> endTime
job.clientId -> clientId
job.name -> name
job.startAddressType -> startAddressType
job.startAddress -> startAddress
job.startCity -> startCity
job.startProvince -> startProvince
Job.update(job)
}
}
I'm stumped because if I try job.name -> name nothing happens and if I try job.name = name then I get a Scala reassignment to val error. I get the same error when trying var name instead of val name.
It's obviously a syntax issue on my part, what's the proper way to handle this? Thanks!
More Info: if this helps, here's the Job class used in our Play! app:
class Job(
val id: Long,
#Column("name")
val name: String,
#Column("end_time")
val endTime: Timestamp,
#Column("start_time")
val startTime: Timestamp,
#Column("client_id")
val clientId: Long,
#Column("start_address_type")
var startAddressType:String,
#Column("start_address")
var startAddress: String,
/* LOTS MORE LIKE THIS */
) extends KeyedEntity[Long] {
}
job.name is an immutable property, so you cannot change its value with job.name = name. You can see in the definition of the Job class that name is declared with val, meaning its value is immutable and can never be changed. The only way to "change" the values of the job object is to actually create a totally new instance and discard the old one. This is standard practice when dealing with immutable objects.
Changing your local name from val to var won't matter, since you are only reading the value of that variable.
val are immutable, in fat the whole Job class is immutable (since all fields are).
What could be done is to create a case class JobW and a bit of pimping to allow the use of copy. That said:
class Job(val id:Long, val name:String) {}
case class JobW(override val id:Long, override val name:String) extends Job(id, name){
def ok:String = name + id
}
implicit def wrapJob(job:Job):JobW = JobW(job.id, job.name)
val job:Job = new Job(2L, "blah")
println(job.ok)
println(job.copy(name="Blob"))
What I've done, is to wrap a (spimplified for the exercise) Job into a case class wrapper, and define the implicit conversion.
Using this implicit conversion (what is called pimping), you'll have access to the ok method but also the copy one.
The copy method is an injected one on case classes, that takes as much arguments as the case class as fields and produces a new instance of the case class.
So you have now the ability to change only one value of you class, very simply I mean, and retrieve an new object (as functional programming arges for immutability).
Related
I have a case class for configuration parameters which is populated (using NO external library) before starting the actual application.
I pass this config object through out the application and in too many places.
Now the question is can this object be made global so I can refer it across the application as the values are going to be constant.
case class ConfigParam() extends Serializable {
var JobId: Int = 0
var jobName: String = null
var snapshotDate: Date = null
}
val configParam = ???
val ss = getSparkSession(configParam) //Method call...
Using ConfigParam as a global object could have bad implications for you. First of all, it will make harder to test any function which is using that global object.
Maybe you could just pass ConfigParam as an implicit argument?
For example, let's say you've got 3 functions:
def funA(name: String)(implicit configParam: ConfigParam): String = ???
def funB(number: Int)(implicit configParam: ConfigParam): String = ???
//you don't have to explicitily pass config param to funA or funB
def funC(name: String)(implicit configParam: ConfigParam): String = funA(name) + funB(100)
implicit val configParam = ??? //you need to initialise configParams as implicit val
funC("somename") //you can now just call funC without explicitly passing configParam
//it will be also passed to all function calls inside funC
//as long as they've got implicit parameter list with ConfigParam
Another solution could be to use some kind of dependency-injection framework, like guice.
class Person(){
val name : String
def this(n : String) {
this()
this.name = n
}
}
compile time error : reassignment to val
i am a newbie to scala and so far i learned how to use primary constructor and case classes for initialization of data members. I am just wandering, if there is a way to initialize val data member inside this. Initialization of var data member works fine below :-
class Person(){
var name : String = _
def this(n : String) {
this()
this.name = n
}
}
You just can't assign to a val after initialization. In Scala the body of the class IS the constructor, you can see examples here.
In general, you just define all variables in the primary constructor itself as "class parameters": class Person(val name: String) if you need to receive the name for initialization or class Person() { val name = 'Joe' } if it is fixed.
This can be quite surprising coming from Java, as you are used to have constructors that produce the values and build the object directly. For such a case, the best solution is to use apply methods on a companion object:
class Person(val name: String)
object Person() {
def apply(db: SomeDBConnectionWrapper, id: Int) = new Person(db.fetchName(id))
}
That allows you to call Person(db, 3) to get a new person with custom initialization, but the constructor itself still receives all it needs to construct a new instance where all values are only assigned once.
I'm trying to write a convenience function that replaces the left tree of an immutable binary tree, and I'm getting "Error occurred in an application involving default arguments" in the following replaceL method:
abstract class AbNode {
val key = null
val value = null
val leftTree:AbNode = NullNode
val rightTree:AbNode = NullNode
}
case class Node[K <:Ordered[K],V](k:K, v:V, lT:AbNode, rT:AbNode) extends AbNode {
val key:K = k
val value:V = v
val leftTree:AbNode = lT
val rightTree:AbNode = rT
}
object Node {
def replaceL[K <: Ordered[K],V](newTree:AbNode, node:Node[K,V]): Node[K,V] =
node.copy(leftTree = newTree) //<< Error occurs here
}
case object NullNode extends AbNode {
val key = null
val value = null
val leftTree = NullNode
val rightTree = NullNode
}
The copy method (and default parameters in general) use the name used in the constructor, not the field name that you assign it to (I don't know why this didn't click sooner).
In the case of a case class, the assigned fields are useless; as far as I can tell, they're simply holding a copy of a reference to the constructor value (not my original intent). I think my confusion stemmed from the fact that in C-style languages, the variables given to a constructor are later assigned to a field. In other words, the way I have my classes set-up is non-sensical, they shouldn't have any fields.
My Node class should be simply:
case class Node[K <:Ordered[K],V](k:K, v:V, leftTree:AbNode, rightTree:AbNode) extends AbNode
Which allows copy to see the value I'm referring to.
I have:
case class One(someParam: String) {
private val _defaultTimeout = readFromConfig("defaultTimeout")
val timeout: Timeout = akka.util.Timeout(_defaultTimeout seconds)
val info: Option[Info] = Await.result(someSmartService.getInformationForSomething(someParam)), timeout.duration)
}
I'm building a service, which will obscure (encrypt) some sensitive data. I'm doing it in a such way:
def encrypt(oldOne: One): One = {
val encryptedSomeParam = EncryptService.getHash(oldOne.someParam)
val encryptedInfo = encryptInfo(oldOne.info)
// what to do with that? ^^
one.copy(someParam = encryptedSomeParam)
}
Also, I need to encrypt some data inside this "info" field of class One. The issue is that it is a val and I cannot reassign the value of a val. Is there an easy way how to do that? For now I'm thinking about changing it to a var, but I think it's not the best way to do that. Also, I cannot write encrypted data to this value from the beginning like this:
val info: Option[Info] = EncryptionService.encrypt(someSmartService.getInformationForSomething(someParam))
As this field is used in other places where I need the fields to be not encrypted. I want to encrypt sensitive data before the persistence of the object to a database.
Any ideas?
Thanks in advance!
EDIT: I know, that this looks like a bad design, so if someone has a better idea how to deal with it I'm looking forward to hear from you :)
Why not make info a case class argument as well?
case class One(someParam: String, info: Option[Info])
You could implement a default value for info by defining the companion object like
object One {
def apply(someParam: String): One = One(someParam, someSmartService.getInformationForSomething(someParam))
}
That would allow you to work with Ones as follows:
One("foo")
One("foo", Some(...))
One(encryptedSomeParam, encryptedInfo)
One("plaintext").copy(someParam = encryptedSomeParam, info = encryptedInfo)
EDIT 1: Lazy info
Case classes cannot have lazy val arguments, i.e., neither info: => Option[String] nor lazy val info: Option[String] is allowed as an argument type.
You could make info a parameter-less function, though
case class One(someParam: String, info: () => Option[String])
object One {
def apply(someParam: String): One = One(someParam, () => Some(someParam))
}
and then use it as
One("hi", () => Some("foo"))
println(One("hi", () => None).info())
This is obviously not ideal since it is not possible to introduce these changes without breaking code client code. Better solutions are welcome.
EDIT 2: Lazy info, no case class
If you don't insist on One being a case class (for example, because you really need copy), you could use a regular class with lazy values and a companion object for easy use:
class One(_someParam: String, _info: => Option[String]) {
val someParam = _someParam
lazy val info = _info
}
object One {
def apply(someParam: String): One = new One(someParam, Await.result(...))
def apply(someParam: String, info: => Option[String]): One = new One(someParam, info)
def unapply(one: One) = Some((one.someParam, one.info))
}
Schema.org is markup vocabulary (for the web) and defines a number of types in terms of properties (no methods). I am currently trying to model parts of that schema in Scala as internal model classes to be used in conjunction with a document-oriented database (MongoDB) and a web framework.
As can be seen in the definition of LocalBusiness, schema.org uses multiple inheritance to also include properties from the "Place" type. So my question is: How would you model such a schema in Scala?
I have come up with two solutions so far. The first one use regular classes to model a single inheritance tree and uses traits to mixin those additional properties.
trait ThingA {
var name: String = ""
var url: String = ""
}
trait OrganizationA {
var email: String = ""
}
trait PlaceA {
var x: String = ""
var y: String = ""
}
trait LocalBusinessA {
var priceRange: String = ""
}
class OrganizationClassA extends ThingA with OrganizationA {}
class LocalBusinessClassA extends OrganizationClassA with PlaceA with LocalBusinessA {}
The second version tries to use case classes. However, since case class inheritance is deprecated, I cannot model the main hierarchy so easily.
trait ThingB {
val name: String
}
trait OrganizationB {
val email: String
}
trait PlaceB {
val x: String
val y: String
}
trait LocalBusinessB {
val priceRange: String
}
case class OrganizationClassB(val name: String, val email: String) extends ThingB with OrganizationB
case class LocalBusinessClassB(val name: String, val email: String, val x: String, val y: String, val priceRange: String) extends ThingB with OrganizationB with PlaceB with LocalBusinessB
Is there a better way to model this? I could use composition similar to
case class LocalBusinessClassC(val thing:ThingClass, val place: PlaceClass, ...)
but then of course, LocalBusiness cannot be used when a "Place" is expected, for example when I try to render something on Google Maps.
What works best for you depends greatly on how you want to map your objects to the underlying datastore.
Given the need for multiple inheritance, and approach that might be worth considering would be to just use traits. This gives you multiple inheritance with the least amount of code duplication or boilerplating.
trait Thing {
val name: String // required
val url: Option[String] = None // reasonable default
}
trait Organization extends Thing {
val email: Option[String] = None
}
trait Place extends Thing {
val x: String
val y: String
}
trait LocalBusiness extends Organization with Place {
val priceRange: String
}
Note that Organization extends Thing, as does Place, just as in schema.org.
To instantiate them, you create anonymous inner classes that specify the values of all attributes.
object UseIt extends App {
val home = new Place {
val name = "Home"
val x = "-86.586104"
val y = "34.730369"
}
val oz = new Place {
val name = "Oz"
val x = "151.206890"
val y = "-33.873651"
}
val paulis = new LocalBusiness {
val name = "Pauli's"
override val url = "http://www.paulisbarandgrill.com/"
val x = "-86.713660"
val y = "34.755092"
val priceRange = "$$$"
}
}
If any fields have a reasonable default value, you can specify the default value in the trait.
I left fields without value as empty strings, but it probably makes more sense to make optional fields of type Option[String], to better indicate that their value is not set. You liked using Option, so I'm using Option.
The downside of this approach is that the compiler generates an anonymous inner class every place you instantiate one of the traits. This could give you an explosion of .class files. More importantly, though, it means that different instances of the same trait will have different types.
Edit:
In regards to how you would use this to load objects from the database, that depends greatly on how you access your database. If you use an object mapper, you'll want to structure your model objects in the way that the mapper expects them to be structured. If this sort of trick works with your object mapper, I'll be surprised.
If you're writing your own data access layer, then you can simply use a DAO or repository pattern for data access, putting the logic to build the anonymous inner classes in there.
This is just one way to structure these objects. It's not even the best way, but it demonstrates the point.
trait Database {
// treats objects as simple key/value pairs
def findObject(id: String): Option[Map[String, String]]
}
class ThingRepo(db: Database) {
def findThing(id: String): Option[Thing] = {
// Note that in this way, malformed objects (i.e. missing name) simply
// return None. Logging or other responses for malformed objects is left
// as an exercise :-)
for {
fields <- db.findObject(id) // load object from database
name <- field.get("name") // extract required field
} yield {
new Thing {
val name = name
val url = field.get("url")
}
}
}
}
There's a bit more to it than that (how you identify objects, how you store them in the database, how you wire up repository, how you'll handle polymorphic queries, etc.). But this should be a good start.