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
Related
My use case has case classes something like
case class Address(name:String,pincode:String){
override def toString =name +"=" +pincode
}
case class Department(name:String){
override def toString =name
}
case class emp(address:Address,department:Department)
I want to create a DSL like below.Can anyone share the links about how to create a DSL and any suggestions to achieve the below.
emp.withAddress("abc","12222").withDepartment("HR")
Update:
Actual use case class may have more fields close to 20. I want to avoid redudancy of code
I created a DSL using reflection so that we don't need to add every field to it.
Disclamer: This DSL is extremely weakly typed and I did it just for fun. I don't really think this is a good approach in Scala.
scala> create an Employee where "homeAddress" is Address("a", "b") and "department" is Department("c") and that_s it
res0: Employee = Employee(a=b,null,c)
scala> create an Employee where "workAddress" is Address("w", "x") and "homeAddress" is Address("y", "z") and that_s it
res1: Employee = Employee(y=z,w=x,null)
scala> create a Customer where "address" is Address("a", "b") and "age" is 900 and that_s it
res0: Customer = Customer(a=b,900)
The last example is the equivalent of writing:
create.a(Customer).where("address").is(Address("a", "b")).and("age").is(900).and(that_s).it
A way of writing DSLs in Scala and avoid parentheses and the dot is by following this pattern:
object.method(parameter).method(parameter)...
Here is the source:
// DSL
object create {
def an(t: Employee.type) = new ModelDSL(Employee(null, null, null))
def a(t: Customer.type) = new ModelDSL(Customer(null, 0))
}
object that_s
class ModelDSL[T](model: T) {
def where(field: String): ValueDSL[ModelDSL2[T], Any] = new ValueDSL(value => {
val f = model.getClass.getDeclaredField(field)
f.setAccessible(true)
f.set(model, value)
new ModelDSL2[T](model)
})
def and(t: that_s.type) = new { def it = model }
}
class ModelDSL2[T](model: T) {
def and(field: String) = new ModelDSL(model).where(field)
def and(t: that_s.type) = new { def it = model }
}
class ValueDSL[T, V](callback: V => T) {
def is(value: V): T = callback(value)
}
// Models
case class Employee(homeAddress: Address, workAddress: Address, department: Department)
case class Customer(address: Address, age: Int)
case class Address(name: String, pincode: String) {
override def toString = name + "=" + pincode
}
case class Department(name: String) {
override def toString = name
}
I really don't think you need the builder pattern in Scala. Just give your case class reasonable defaults and use the copy method.
i.e.:
employee.copy(address = Address("abc","12222"),
department = Department("HR"))
You could also use an immutable builder:
case class EmployeeBuilder(address:Address = Address("", ""),department:Department = Department("")) {
def build = emp(address, department)
def withAddress(address: Address) = copy(address = address)
def withDepartment(department: Department) = copy(department = department)
}
object EmployeeBuilder {
def withAddress(address: Address) = EmployeeBuilder().copy(address = address)
def withDepartment(department: Department) = EmployeeBuilder().copy(department = department)
}
You could do
object emp {
def builder = new Builder(None, None)
case class Builder(address: Option[Address], department: Option[Department]) {
def withDepartment(name:String) = {
val dept = Department(name)
this.copy(department = Some(dept))
}
def withAddress(name:String, pincode:String) = {
val addr = Address(name, pincode)
this.copy(address = Some(addr))
}
def build = (address, department) match {
case (Some(a), Some(d)) => new emp(a, d)
case (None, _) => throw new IllegalStateException("Address not provided")
case _ => throw new IllegalStateException("Department not provided")
}
}
}
and use it as emp.builder.withAddress("abc","12222").withDepartment("HR").build().
You don't need optional fields, copy, or the builder pattern (exactly), if you are willing to have the build always take the arguments in a particular order:
case class emp(address:Address,department:Department, id: Long)
object emp {
def withAddress(name: String, pincode: String): WithDepartment =
new WithDepartment(Address(name, pincode))
final class WithDepartment(private val address: Address)
extends AnyVal {
def withDepartment(name: String): WithId =
new WithId(address, Department(name))
}
final class WithId(address: Address, department: Department) {
def withId(id: Long): emp = emp(address, department, id)
}
}
emp.withAddress("abc","12222").withDepartment("HR").withId(1)
The idea here is that each emp parameter gets its own class which provides a method to get you to the next class, until the final one gives you an emp object. It's like currying but at the type level. As you can see I've added an extra parameter just as an example of how to extend the pattern past the first two parameters.
The nice thing about this approach is that, even if you're part-way through the build, the type you have so far will guide you to the next step. So if you have a WithDepartment so far, you know that the next argument you need to supply is a department name.
If you want to avoid modifying the origin classes you can use implicit class, e.g.
implicit class EmpExtensions(emp: emp) {
def withAddress(name: String, pincode: String) {
//code omitted
}
// code omitted
}
then import EmpExtensions wherever you need these methods
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.
I have:
var targets = mutable.HashMap[String, WordCount]()
Where WordCount is a case class:
case class WordCount(name: String,
id: Int,
var count: Option[Double]) {
def withCount(v: Double) : WordCount = copy(count = Some(v))
}
And I'm trying to update the values for count each time the key exists in the map,
def insert(w1: String, w2: String, count: Double) = {
if(targets.contains(w1)){
var wc = targets.get(w1).getOrElse().asInstanceOf[WordCount]
wc.withCount(9.0)
} else{
targets.put(w1, WordCount(w1, idT(), Some(0.0))
}
}
But it does not working. What is the proper way to do that? please!
Calling withCount does not modify the case class instance but creates a new one. Therefore, you'll have to store the newly created instance in the map again:
def insert(w1: String, w2: String, count: Double) = {
val newWC = targets.get(w1).fold {
WordCount(w1, idT(), Some(0.0)
} { oldWC =>
oldWC.withCount(9.0)
}
targets.put(w1, newWC)
}
Note the targets.get(w1).fold: Get returns an Option[WordCount], fold calls its first parameter, if its receiver is a None, otherwise (i.e. its a Some) it calls the second parameter and passes it the value the Some contains.
Is it possible to add functionality before calling constructor in extra constructor in scala ?
Lets say, I have class User, and want to get one string - and to split it into attributes - to send them to the constructor:
class User(val name: String, val age: Int){
def this(line: String) = {
val attrs = line.split(",") //This line is leading an error - what can I do instead
this(attrs(0), attrs(1).toInt)
}
}
So I know I'm not able to add a line before sending to this, because all constructors need to call another constructor as the first statement of the constructor.
Then what can I do instead?
Edit:
I have a long list of attributes, so I don't want to repeat line.split(",")
I think this is a place where companion object and apply() method come nicely into play:
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
class User(val name: String, val age: Int)
Then you just create your object the following way:
val u1 = User("Zorro,33")
Also since you're exposing name and age anyway, you might consider using case class instead of standard class and have consistent way of constructing User objects (without new keyword):
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
case class User(name: String, age: Int)
val u1 = User("Zorro,33")
val u2 = User("Zorro", "33")
Ugly, but working solution#1:
class User(val name: String, val age: Int){
def this(line: String) = {
this(line.split(",")(0), line.split(",")(1).toInt)
}
}
Ugly, but working solution#2:
class User(val name: String, val age: Int)
object User {
def fromString(line: String) = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
Which can be used as:
val johny = User.fromString("johny,35")
You could use apply in place of fromString, but this will lead to a confusion (in one case you have to use new, in the other you have to drop it) so I prefer to use different name
Another ugly solution:
class User(line: String) {
def this(name: String, age: Int) = this(s"$name,$age")
val (name, age) = {
val Array(nameStr,ageStr) = line.split(",")
(nameStr,ageStr.toInt)
}
}
But using a method of the companion object is probably better.
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).