In the book "Scala for the impatient" by Cay Horstmann, on chapter 5 exercise 8, there is this question:
//Make a class Car with read-only properties for manufacturer, model name,
//and model year, and a read-write property for the license plate. Supply four
// constructors. All require the manufacturer and model name. Optionally,
//model year and license plate can also be specified in the constructor. If not,
//the model year is set to -1 and the license plate to the empty string. Which
//constructor are you choosing as the primary constructor? Why?
So I coded the class:
case class Car(val manufacturer: String, val modelName: String,
val modelYear: Int, var licensePlate : String = "") {
def this(manufacturer: String, modelName: String, licensePlate: String) {
this(manufacturer, modelName, -1, licensePlate)
}
def this(manufacturer: String, modelName: String, modelYear: Int) {
specifically on this part:
this(manufacturer, modelName, modelYear)
}
def this(manufacturer: String, modelName: String) {
this(manufacturer, modelName, -1)
}
}
The compiler complains of the error:
<console>:14: error: called constructor's definition must precede calling constructor's definition
this(manufacturer, modelName, modelYear)
^
Why does it complain when I have provided a default value for the license Plate as an empty String in the primary constructor, and it sure is defined first??
The error goes away if I do this:
this(manufacturer, modelName, modelYear, "")
or if I make the class primary constructor not have a default value for the licensePlate (of course adjusting other auxilliary constructor calls in the process).
Note that the question specifically asked for 4 constructors, so instance creation can be called like:
new Car("Honda", "City", 2010, "ABC-123")
new Car("Honda", "City")
new Car("Honda", "City", "ABC-123")
new Car("Honda", "City", 2010)
Thanks in advance to those who can shed light on this issue.
====
Thanks to answers by Ende Neu and Kigyo, this looks like the perfect solution (remove the recursive constructor):
case class Car(val manufacturer: String, val modelName: String,
val modelYear: Int = -1, var licensePlate : String = "") {
def this(manufacturer: String, modelName: String, licensePlate: String) {
this(manufacturer, modelName, -1, licensePlate)
}
<< removed constructors here >>
}
Console println new Car("Honda", "City", 2010, "ABC-123")
Console println new Car("Honda", "City")
Console println new Car("Honda", "City", "ABC-123")
Console println new Car("Honda", "City", 2010)
I just want to make some things clear.
You can leave out the last parameter in this example. Here we call the primary constructor, that has 3 parameters, with only supplying two. (related to Ende Neu's example)
case class Test(a: String, b: String, c: String = "test") {
def this() = this("", "")
}
So why does it not work in your scenario? Have a close look!
def this(manufacturer: String, modelName: String, modelYear: Int) {
this(manufacturer, modelName, modelYear)
}
Here you also leave out the last parameter, but the new constructor you define takes exactly that amount of parameters and this makes it a recursive call. So you never call the primary constructor and therefore you get the error.
I would also remove the default value in your case. Maybe there is a way around it, but I'm too tired at the moment.
You get this error because the scala compiler is not able to see the default value you provided in your case class and you are declaring a constructor with three parameters instead of four, for example:
case class Test(a: String, b: String, c: String = "test") {
def this(d: String, e: String) = this(c, e)
}
This will throw the same error, what you can do is to specify default values in the constructor as you did with the empty string:
def this(d: String) = this(d, "empty")
def this(d: String, e: String) = this(d, e, "empty") // and this would return a new object
Having a case class with default value simply corresponds to a constructor where you don't have to serve all parameters, so for the exercise you don't need two constructors, if the year and the plate are not specified use some predifined value, else use the default constructor (which takes already 4 parameters):
case class Car(val manufacturer: String, val modelName: String, val modelYear: Int, var licensePlate : String) {
def this(manufacturer: String, modelName: String) =
this(manufacturer, modelName, -1, "")
}
}
And then call it like this:
new Car("Wolksvagen", "Polo")
Car("Ford", "Fiesta", 1984, "plate")
Note that you have to use the new keyword in the first declaration because it's not referring to the apply method in the companion object (which could also be overridden, see this SO question).
Related
I have a Scala case class with the following declaration:
case class Student(name: String, firstCourse: String, secondCourse: String, thirdCourse: String, fourthCourse: String, fifthCourse: String, sixthCourse: String, seventhCourse: String, eighthCourse: String)
Before I create a new Student object, I have a variable holding the value for name and an array holding the values for all 8 courses. Is there a way to pass this array to the Student constructor? I want it to look cleaner than:
val firstStudent = Student(name, courses(0), courses(1), courses(2), courses(3), courses(4), courses(5), courses(6), courses(7))
You can always write your own factory methods on the Student companion object:
case class Student(
name: String, firstCourse: String, secondCourse: String,
thirdCourse: String, fourthCourse: String,
fifthCourse: String, sixthCourse: String,
seventhCourse: String, eighthCourse: String
)
object Student {
def apply(name: String, cs: Array[String]): Student = {
Student(name, cs(0), cs(1), cs(2), cs(3), cs(4), cs(5), cs(6), cs(7))
}
}
and then just call it like this:
val courses: Array[String] = ...
val student = Student("Bob Foobar", courses)
Why you need a case class with 8 similar fields is another question. Something with automatic mappings to a database of some kind?
I'm trying to make this statement work in my Scala code:
val dvd1 = Item(new Description("The Matrix DVD", 15.50, "DVD World"))
I have the following classes and companion object:
class Item(){
private var id = 0
def getId(): Int = this.id
}
object Item{
def apply(description: String, price: Double, supplier: String): Description = {
new Description(description, price, supplier)
}
def nextId: Int = {
this.id += 1
}
}
class Description(description: String, price: Double, supplier: String){
def getDescription(): String = description
def getPrice(): Double = price
def getSupplier(): String = supplier
}
And I get the following error with my apply function and nextId:
error: not enough arguments for method apply: (description: String, price: Double, supplier: String)Description in object Item.
Unspecified value parameters price, supplier.
val dvd1 = Item(new Description("The Matrix DVD", 15.50, "DVD World"))
^
indus.scala:16: error: value id is not a member of object Item
this.id += 1
^
It's not apparent to me what I'm doing wrong.
Question: What do I need to change with my apply function so that dvd1 will work as expected. Also, nextId should increment the Item's id when Item.nextId is called, what's wrong there?
1) you are trying to access class data from companion object which you can't.
scala> case class Order(id: String)
defined class Order
scala> object Order { println(id) }
<console>:11: error: not found: value id
object Order { println(id) }
^
The reverse works, once you import your companion object inside class.
2) you when define apply inside your companion, you you have two apply functions now, one with empty args and one with three args as you defined which you want to call. Your args is wrong.
Based on your comments below, you want to have Item has Description datastructure which can be done as below using immutable classes in scala called case class
import scala.util.Random
case class Description(description: String, price: Double, supplier: String)
case class Item(id: Int, description: Description)
object Item {
def apply(description: String, price: Double, supplier: String): Item = {
val itemId = Random.nextInt(100)
new Item(itemId, Description(description, price, supplier))
}
}
//client code
val dvd1 = Item("The Matrix DVD", 15.50, "DVD World")
assert(dvd1.isInstanceOf[Item])
assert(dvd1.description.description == "The Matrix DVD")
assert(dvd1.description.price == 15.50)
assert(dvd1.description.supplier == "DVD World")
see code here in online scala editor - https://scastie.scala-lang.org/prayagupd/P3eKKPLnQYqDU2faESWtfA/4
Also, read case class explanation
I have following classes
case class User(userId: Int, userName: String, email: String,
password:
String) {
def this() = this(0, "", "", "")
}
case class Team(teamId: Int, teamName: String, teamOwner: Int,
teamMembers: Seq[User]) {
def this() = this(0, "", 0, Nil)
}
I would like to add or User in teamMembers: Seq[User]. I tried couple of ways as:
Team.teamMembers :+ member
Team.teamMembers +: member
Nothing works :). Pleas advice me how can I add or remove item from teamMembers: Seq[User].
Thanks in advance!
You didn't mention which Seq do you use.
If it's scala.collection.mutable.Seq you can add to this Seq.
But, most changes that you use immutable.Seq which is Scala's default. This means you cannot add to a it, but you can create a new one with all items + new item.
With scala out of the box you can do it like this -
val team =Team(0,"", 0, Seq[User]())
val member = User(0, "","", "")
val teamWithNewMemebr = team.copy(teamMembers = team.teamMembers :+ member)
But this becomes pretty ugly if you have a lot of nesting or you have to do it a lot.
To overcome this complicated syntax you can use libraries like scalaz, monocle which provides you Lenses
Here's a good sample of how to use Lenses http://eed3si9n.com/learning-scalaz/Lens.html
Create an operation that returns a new Team with the member added, e.g.
The problem with your other code I think is you are trying to change an immutable variable. The teamMember field in the case class team is an immutable val and so changing it with an operation will not change what is contained in it - it will just return a new sequence with the value appended, but won't affect the one in the case class Team.
case class Team(teamId: Int, teamName: String, teamOwner: Int, teamMembers: Seq[User]) {
def this() = this(0, "", 0, Nil)
// Operation returns a new Team object which has all elements of the previous team plus an additional member appended to the team members.
def addMember(member: User) : Team = Team(teamId, teamName, teamOwner, teamMembers :+ member)
}
From the doc for the plus (+) operator:
[use case] A copy of the sequence with an element prepended
so + will yield a new collection with your additional element prepended. Are you using this new collection.
Well... all the parameter attributes in the case classes are immutable by default.
This is done with the purpose of promoting thread-safe programming. Also, one major thing that should be noticed is that this in a way also promotes the original idea of OOP ( the one similar to Smalltalk, before being transformed by Java OOP ).
Well... Separation of state and behaviour. So... basically kind of the ideal situation where Separation of state and behaviour meets thread-safety.
My personal taste for doing this would be - Have state in the case class, and move all behaviour to companion object.
case class User( userId: Int, userName: String, email: String, password: String )
object User {
def apply(): User = User( 0, "", "", "" )
}
case class Team( teamId: Int, teamName: String, teamOwner: Int, teamMembers: Seq[ User ] )
object Team {
def apply(): Team = Team( 0, "", 0, Nil )
// since addMember is a behavior, it belongs here.
// Also... since we are immutable... addMember name does not make much sense...
// Let's call it withMember
def withMember( team: Team, user: User ): Team = {
team.copy( teamMembers = team.teamMembers :+ user )
}
}
Now, you will have to use it like this,
val user = User()
val team = Team()
val teamWithMember = Team.withMember( team, user )
But... In case... ( like in a really rare case ), if you "really" want ( control your desires man... control ) to have it mutable then
case class Team( teamId: Int, teamName: String, teamOwner: Int, var teamMembers: Seq[ User ] )
object Team {
def apply(): Team = Team( 0, "", 0, Nil )
// since addMember is a behavior, it belongs here.
// Now we can keep name addMember
def addMember( team: Team, user: User ): Unit = {
team.teamMembers = team.teamMembers :+ user
}
}
And use it like this,
val user = User()
val team = Team()
team.addMember( user )
I am starting a new project using Scala and Akka and am having trouble writing tests. In my tests I am checking the equality of two List objects using should equal:
actualBook should equal (expectedBook)
Everything in my test suite compiles and runs, but the tests fail with the following message:
org.scalatest.exceptions.TestFailedException: List(BookRow(A,100.0,10.6)) did not equal List(BookRow(A,100.0,10.6))
Clearly the tests are passing (i.e., both List objects contain the same contents). Not sure if this is relevant or not, but actualBook and expectedBook have the same hash code (and actualBook(0) and expectedBook(0) also have the same hash code).
I am wondering if the problem is due to...
my using the incorrect comparison operator
the fact that I have not explicitly defined a way to compare BookRow objects.
For reference here is the code for my tests:
package lob
import cucumber.api.DataTable
import org.scalatest.Matchers._
import scala.collection.JavaConversions._
import cucumber.api.java.en.{When, Then}
class OrderBookSteps {
val orderTypes = OrderType.all()
val buyBook: OrderBook = new OrderBook(Bid, orderTypes)
val sellBook: OrderBook = new OrderBook(Ask, orderTypes)
#When("""^the following orders are added to the "(.*?)" book:$""")
def ordersAddedToBook(sideString: String, orderTable: DataTable) {
val (side, book) = getBook(sideString)
val orders = orderTable.asList[OrderRow](classOf[OrderRow]).toList.map(
r => LimitOrder(r.broker, side, r.volume, r.price.toDouble))
orders.foreach(book.add)
}
#Then("""^the "(.*?)" order book looks like:$""")
def orderBookLooksLike(sideString: String, bookTable: DataTable) {
val (_, book) = getBook(sideString)
val expectedBook = bookTable.asList[BookRow](classOf[BookRow]).toList
val actualBook = book.orders().map(o => BookRow(o.broker, o.volume, orderTypes(o).bookDisplay))
actualBook should equal (expectedBook)
}
def getBook(side: String) = side match {
case "Bid" => (Bid, buyBook)
case "Ask" => (Ask, sellBook)
}
case class OrderRow(broker: String, volume: Double, price: String)
case class BookRow(broker: String, volume: Double, price: String)
}
You can try:
List(BookRow(A,100.0,10.6)).toSeq should equal (List(BookRow(A,100.0,10.6)).toSeq)
Or:
List(BookRow(A,100.0,10.6) should contain theSameElementsAs List(BookRow(A,100.0,10.6))
Assuming you have BookRow (normal class) equals overridden.
I have found a solution, although I don't understand why it works! I just needed to replace:
case class OrderRow(broker: String, volume: Double, price: String)
case class BookRow(broker: String, volume: Double, price: String)
with
private case class OrderRow(broker: String, volume: Double, price: String)
private case class BookRow(broker: String, volume: Double, price: String)
I would like to know why this works.
Suppose, I have my domain object named "Office":
case class Office(
id: Long,
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
null.asInstanceOf[Long], name, phone, address
)
}
When I create new Office:
new Office("officeName","00000000000", "officeAddress")
I don't specify id field becouse I don't know it. When I save office (by Anorm) I now id and do that:
office.id = officeId
So. I know that using null is non-Scala way. How to avoid using null in my case?
UPDATE #1
Using Option.
Suppose, something like this:
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
And, after saving:
office.id = Option(officeId)
But what if I need to find something by office id?
SomeService.findSomethingByOfficeId(office.id.get)
Does it clear? office.id.get looks not so good)
UPDATE #2
Everyone thanks! I've got new ideas from your answers! Greate thanks!
Why not declare the id field as a Option? You should really avoid using null in Scala. Option is preferable since it is type-safe and plays nice with other constructs in the functional paradigm.
Something like (I haven't tested this code):
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
Just make the id field an Option[Long]; once you have that, you can use it like this:
office.id.map(SomeService.findSomethingByOfficeId)
This will do what you want and return Option[Something]. If office.id is None, map() won't even invoke the finder method and will immediately return None, which is what you want typically.
And if findSomethingByOfficeId returns Option[Something] (which it should) instead of just Something or null/exception, use:
office.id.flatMap(SomeService.findSomethingByOfficeId)
This way, if office.id is None, it will, again, immediately return None; however, if it's Some(123), it will pass that 123 into findSomethingByOfficeId; now if the finder returns a Some(something) it will return Some(something), if however the finder returns None, it will again return None.
if findSomethingByOfficeId can return null and you can't change its source code, wrap any calls to it with Option(...)—that will convert nulls to None and wrap any other values in Some(...); if it can throw an exception when it can't find the something, wrap calls to it with Try(...).toOption to get the same effect (although this will also convert any unrelated exceptions to None, which is probably undesirable, but which you can fix with recoverWith).
The general guideline is always avoid null and exceptions in Scala code (as you stated); always prefer Option[T] with either map or flatMap chaining, or using the monadic for syntactic sugar hiding the use of map and flatMap.
Runnable example:
object OptionDemo extends App {
case class Something(name: String)
case class Office(id: Option[Long])
def findSomethingByOfficeId(officeId: Long) = {
if (officeId == 123) Some(Something("London")) else None
}
val office1 = Office(id = None)
val office2 = Office(id = Some(456))
val office3 = Office(id = Some(123))
println(office1.id.flatMap(findSomethingByOfficeId))
println(office2.id.flatMap(findSomethingByOfficeId))
println(office3.id.flatMap(findSomethingByOfficeId))
}
Output:
None
None
Some(Something(London))
For a great introduction to Scala's rather useful Option[T] type, see http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html.
When using id: Option[Long] , extract the option value for instance with
if (office.id.isDefined) {
val Some(id) = office.id
SomeService.findSomethingByOfficeId(id)
}
or perhaps for instance
office.id match {
case None => Array()
case Some(id) => SomeService.findSomethingByOfficeId(id)
}
Also you can define case classes and objects as follows,
trait OId
case object NoId extends OId
case class Id(value: Long) extends OId
case class Office (
id: OId = NoId,
name: String,
phone: String,
address: String
)
Note that by defaulting id for example to NoId , there is no need to declare a call to this. Then
val office = Office (Id(123), "name","phone","addr")
val officeNoId = Office (name = "name",phone="phone",address="addr")
If the id member is defined last, then there is no need to name the member names,
val office = Office ("name","phone","addr")
office: Office = Office(name,phone,addr,NoId)
As of invoking (neatly) a method,
office.id match {
case NoId => Array()
case Id(value) => SomeService.findSomethingByOfficeId(value)
}
I prefer more strong restriction for object Id property:
trait Id[+T] {
class ObjectHasNoIdExcepthion extends Throwable
def id : T = throw new ObjectHasNoIdExcepthion
}
case class Office(
name: String,
phone: String,
address: String
) extends Id[Long]
object Office {
def apply(_id : Long, name: String, phone: String, address: String) =
new Office(name, phone, address) {
override def id : Long = _id
}
}
And if I try to get Id for object what is not stored in DB, I get exception and this mean that something wrong in program behavior.
val officeWithoutId =
Office("officeName","00000000000", "officeAddress")
officeWithoutId.id // Throw exception
// for-comprehension and Try monad can be used
// update office:
for {
id <- Try { officeWithoutId.id }
newOffice = officeWithoutId.copy(name = "newOffice")
} yield OfficeDAL.update(id, newOffice)
// find office by id:
for {
id <- Try { officeWithoutId.id }
} yield OfficeDAL.findById(id)
val officeWithId =
Office(1000L, "officeName","00000000000", "officeAddress")
officeWithId.id // Ok
Pros:
1) method apply with id parameter can be incapsulated in DAL logic
private[dal] def apply (_id : Long, name: String, ...
2) copy method always create new object with empty id (safty if you change data)
3) update method is safety (object not be overridden by default, id always need to be specified)
Cons:
1) Special serealization/deserealization logic needed for store id property (json for webservices, etc)
P.S.
this approach is good if you have immutable object (ADT) and store it to DB with id + object version instead object replace.