I am writing my first large Scala program. In the Java equivalent, I have an enum that contains labels and tooltips for my UI controls:
public enum ControlText {
CANCEL_BUTTON("Cancel", "Cancel the changes and dismiss the dialog"),
OK_BUTTON("OK", "Save the changes and dismiss the dialog"),
// ...
;
private final String controlText;
private final String toolTipText;
ControlText(String controlText, String toolTipText) {
this.controlText = controlText;
this.toolTipText = toolTipText;
}
public String getControlText() { return controlText; }
public String getToolTipText() { return toolTipText; }
}
Never mind the wisdom of using enums for this. There are other places that I want to do similar things.
How can I do this in Scala using scala.Enumeration? The Enumeration.Value class takes only one String as a parameter. Do I need to subclass it?
Thanks.
You could do this which matches how enums are used:
sealed abstract class ControlTextBase
case class ControlText(controlText: String, toolTipText: String)
object OkButton extends ControlText("OK", "Save changes and dismiss")
object CancelButton extends ControlText("Cancel", "Bail!")
I'd like to propose the following workaround for the issue:
object ControlText extends Enumeration {
type ControlText = ControlTextValue
case class ControlTextValue(controlText: String, toolTipText: String) extends Val(controlText)
val CANCEL_BUTTON = ControlTextInternalValue("Cancel", "Cancel the changes and dismiss the dialog")
val OK_BUTTON = ControlTextInternalValue("OK", "Save the changes and dismiss the dialog")
protected final def ControlTextInternalValue(controlText: String, toolTipText: String): ControlTextValue = {
ControlTextValue(controlText, toolTipText)
}
}
Now you can use ControlText as Java enum:
val c: ControlText
c.toolTipText
The only a little bad smell is to get enum object by withName or apply methods. You have to do a cast:
val c: ControlText = ControlText.withName(name).asInstanceOf[ControlText]
Following on from Mitch's answer, if you find that the sealed behaviour is not restrictive enough in limiting subclassed instances to the file where the base class is defined, you can use an object (module) definition like this:
object ControlTexts {
sealed abstract class ControlTextBase
case class ControlText private[ControlTexts] (controlText: String,
toolTipText: String)
extends ControlTextBase
object OkButton extends ControlText("OK", "Save changes and dismiss")
object CancelButton extends ControlText("Cancel", "Bail!")
}
which obviously limits further instantiation of ControlText instances. The sealed keyword is still important in helping detect missing cases in pattern matching.
Related
I have a superclass:
class Filter(val param: ComplexFilterParams){
def this(config: String) = this(parseStrConfig(config))
And I need to create a subclass that gets a String argument and then parses it in another way and creates ComplexFilterParams.
Something like that:
class NewFilter(str:String) extends Filter {
Is there a way to do it?
I got one solution. But I think it's ugly. I create companion object, define there a convert method and do next:
class NewFilter(str:String) extends Filter(NewFilter.convert(str)) {
You can go mush easier with another apply implementation in companion object like:
class NewFilter(val param: ComplexFilterParams) extends Filter(param){
//other implementations
}
object NewFilter {
def apply(str: String) = new NewFilter(convert(str))
def convert(str: String): ComplexFilterParams = ...
}
val filter = NewFilter("config string")
I have two classes that extend the same trait :
trait Event {
val propertyCommon : String = ""
}
case class EventA(propertyA : String) extends Event
case class EventB(propertyB : String) extends Event
Now I instantiate my classes in a List :
myList : List[Event] = List(EventA("a"),EventB("b"))
What I want to do is to instantiate after the common property, as if there was a copy method in my trait :
myList.map(_.copy(propertyCommon = "Something"))
How could I do that ?
What you say you want to do is called a prototype pattern in OOP, and as far as I can tell it isn't supported out of the box in Scala (and I think anywhere else).
There are also a few problems:
propertyCommon is not set up using constructor, so each implementation that would have to set it, would most likely have to override it using anonymous class (but then, this property will not be a part of equals, hashcode, toString, derivation, etc)
Event doesn't define any interface that would allow updating it
The easiest (and safest) way I see to implement functionality you want, would be something like this:
trait Event {
val propertyCommon: String // will be set by implementing class
def clone(propertyCommon: String = this.propertyCommon): Event
}
// children define propertyCommon to match the interface
case class EventA(propertyA: String, propertyCommon: String = "") extends Event {
def clone(propertyCommon: String = this.propertyCommon): EventA = copy(propertyCommon)
}
case class EventB(propertyB: String, propertyCommon: String = "") extends Event {
def clone(propertyCommon: String = this.propertyCommon): EventB = copy(propertyCommon)
}
However, probably if we knew more about your problem we could provide some other, simpler solution.
I have an abstract class with a default value for its parameter.
I don't want to have to reuse the default value in the constructor of all the possible implementations.
abstract class Place(val place: String = "World")
class Message(val message: String = "Hello", p: String) extends Place(p) {
override def toString = s"$message $place"
}
What I want to get
new Message("Hi", "Universe") = "Hi Universe" // Ok
new Message("Hi") = "Hi World" // Doesn't work, second parameter is required
new Message() = "Hello World" // Doesn't work, second parameter is required
I considered using an auxiliary constructor omitting the second parameter, but it doesn't help since you can't call super constructors outside of the main constructor.
I want to know how to do it, or why it is not possible. I'm not looking for a workaround, like not using inheritance.
I'm afraid that is not possible. Quite simply, you ARE passing a value to Place constructor, so it wont use the default, whatever its value might be. If you don't mind having a var instead of a val, here is a variant that works for your 3 cases:
abstract class Place(var place: String = "World")
class Message(val message: String = "Hello") extends Place()
{
def this(message: String, place: String) = {
this(message)
this.place = place
}
override def toString = s"$message $place"
}
Constructors in Scala are a little of a mess IMHO. Sometimes a better answer is just to use factory apply() methods on a companion object, which are more flexible.
You can reuse the default value in a more elegant way:
object Place {
val defaultPlace = "World"
}
abstract class Place(val place: String = Place.defaultPlace)
class Message(val message: String = "Hello", p: String = Place.defaultPlace) extends Place(p) {
override def toString = s"$message $place"
}
I have a trait that's implemented by a large number of classes, and I'd like to use the names of the classes that implement this trait at runtime, but with as much code centralized as possible.
Specifically, in my code, I'm using tokens to represent classes to be initialized at runtime. The tokens carry configuration, and the actual class is instantiated as needed via the token, combined with run-time information. For linking with resources outside of my app, I want to be able to access the name of the class for which a token is defined. See the example:
trait Token[Cls] {
val className = ???
// Example generic method depending on final class name
def printClassName = println(className)
}
case class ClassA(t: ClassAToken, runtimeContext: String) {
// a bunch of other code
}
object ClassA {
case class ClassAToken(configParam: String) extends Token[ClassA]
}
So, I'm trying to implement className. Ideally, I can pull this information once at compile time. How can I do this, while keeping boilerplate code out of ClassA? Although, if I can drop the type parameter and get the name of the class implementing the Token trait at runtime, that's great too.
Due to Type Erasure Cls is not available on runtime anymore. To get the informations at runtime, you need to use a TypeTag (in your case a ClassTag).
Your code could look like this:
import scala.reflect._
trait Token[Cls] {
def className(implicit ct: ClassTag[Cls]) = ct.runtimeClass.getName
// Example generic method depending on final class name
def printClassName(implicit ct: ClassTag[Cls]) = println(className)
}
case class ClassA(t: ClassAToken, runtimeContext: String) {
// a bunch of other code
}
object ClassA {
case class ClassAToken(configParam: String) extends Token[ClassA]
}
or if it is possible for you to let Token be an class, you could use the ClassTag context bounds:
import scala.reflect._
class Token[Cls: ClassTag] {
def className = classTag[Cls].runtimeClass.getName
// Example generic method depending on final class name
def printClassName = println(className)
}
case class ClassA(t: ClassAToken, runtimeContext: String) {
// a bunch of other code
}
object ClassA {
case class ClassAToken(configParam: String) extends Token[ClassA]
}
For more informations on TypeTags/ClassTags see Scala: What is a TypeTag and how do I use it?
My question is about scala inheritance details. I have the following code.
package scalasandbox
object Main {
def main(args: Array[String]): Unit = {
val creature: Creature = new Human("First")
creature.rename("Second")
creature.introduce
}
}
class Creature(var name: String) {
def introduce = println("I'm creature: " + name)
def rename(newName: String) = {
println("Creature was renamed to: " + newName)
name = newName
}
}
class Human(name: String) extends Creature(name) {
override def introduce = println("I'm Human: " + name)
}
which produces the following output
Creature was renamed to: Second
I'm human: First
I expect it to be "I'm human: Second" because rename method should change the field value. I have opened Human class with decompiler:
package scalasandbox;
import scala.Predef.;
import scala.ScalaObject;
import scala.collection.mutable.StringBuilder;
import scala.reflect.ScalaSignature;
#ScalaSignature(bytes="\006\001\0212A!\001\002\001\013\t)\001*^7b]*\t1!\001\007tG\006d\027m]1oI\n|\007p\001\001\024\007\0011!\002\005\002\b\0215\t!!\003\002\n\005\tA1I]3biV\024X\r\005\002\f\0355\tABC\001\016\003\025\0318-\0317b\023\tyABA\006TG\006d\027m\0242kK\016$\b\002C\t\001\005\003\005\013\021\002\n\002\t9\fW.\032\t\003'Yq!a\003\013\n\005Ua\021A\002)sK\022,g-\003\002\0301\t11\013\036:j]\036T!!\006\007\t\013i\001A\021A\016\002\rqJg.\033;?)\taR\004\005\002\b\001!)\021#\007a\001%!)q\004\001C!A\005I\021N\034;s_\022,8-Z\013\002CA\0211BI\005\003G1\021A!\0268ji\002")
public class Human extends Creature
implements ScalaObject
{
private final String name;
public void introduce()
{
Predef..MODULE$.println(new StringBuilder().append("I'm Human: ").append(this.name).toString());
}
public Human(String name)
{
super(name);
}
}
and see "private final String name;" there. I think it hides Creature name field. And
Predef..MODULE$.println(new StringBuilder().append("I'm Human: ").append(this.name).toString());
this stuff looks also suspicious because of "this.name" instead of method call "this.name()". Could anyone explain where is my mistake and what is the correct way of implementing those two classes?
The name variable that you use in the Human class resolves to the constructor parameter of Human, and Scala will automatically create private vals for constructor parameters that you use outside the constructor. This is unfortunate in your case. You can prevent this e.g. by naming your parameter in Human differently, e.g. nm:
class Human(nm: String) extends Creature(nm)
try by changing this line:
class Human(foo: String) extends Creature(foo) {
so you don't hide name.
If Creature is only intended for subclassing, I'd advise that you make both the class and the name parameter abstract, as per my answer here:
Idiomatic Scala way to deal with base vs derived class field names?
One advantage to such a technique is that it then becomes far easier to use case classes for the specific, concrete, sub-classes