In Scala how to put constraints on the fields of a class?
In a package I have the domain of my model, in another package I have dsl to instantiate my model.
The basic form of the model is this:
abstract class Element {
var name: String
var description: String
var types : Set[Type]
}
class SAComponent (var name :String,
var description : String,
var properties : Set[Property] = Set(),
var types : Set[Type] = Set(),
) extends Component
Element is the root of my model.
I want to put constraints on the fields of Element, so that each class that inherits name and description and types of Element respects these constraints.
In other words I need to define the I get for these fields. Right?
How should I do?
I tried that, but the constraints are not respected:
abstract class Element {
def name: String
def name_= (value: String): Unit = {if (isBadValue(value)throw new IllegalArgumentException
name = value
}
var description : String,
var types : Set[Type] = Set }
class Component (override var name : String, var description: String) extends Element
The problem is that some fields that must respect the constraints,
in the constructor of the concrete classes must be initialized to a null value. So the "require" for me is not a good solution.
Thank you all.
Checking at initialization does not work for you because you want stateful objects, which can be avoided by using case classes. Instead of mutating the state of an object, you may want to create new objects using copy(field=value), which is automatically generated for case classes.
If you still want to go with stateful objects, I guess you want something like
abstract class Element {
private var _name: String = null
def name_= (value: String) {
require(!isBadValue(value),"Bad Value")
_name = value
}
def name = _name
def isBadValue(value: String): Boolean
}
class Component (initialName : String) extends Element {
name = initialName
def isBadValue(value: String) = value=="name"
}
val c = new Component(null) // works
c.name = "name" // exception
Another thing to point out: the setter generated by override var name in your code overrides your name_= method, which you may already know.
Abstract val:
trait Init {
val name: String
require(!isBadName(name))
def isBadName(name: String) = true
}
Creation:
new { val name = "init" } with Init
Related
Assume I have an interface that provides many immutable properties with default getters, as such
interface Repository {
val name : String
val pattern : String
get() = "foo/bar/baz"
var url : String
get() = "http://mycompanyrepo.com/$name"
fun add() {
TODO("do something interesting here")
}
}
Now, there are several concrete implementations that just use most of the defaults. However, I also want to provide a ConfigurableRepository that allows for more flexibility in other projects that want to configure this at runtime, based on other user parameters.
How can I create a primary constructor that has optional parameters?
I was hoping something along the lines of:
class ConfigurableRepo(var name, var pattern, var url) {
...
}
Edited for clarification
The intent here is to use primary constructor behavior, where the named parameters are optional. More specifically, name, pattern, and url are all optional to anyone calling the constructor, and it will default to the interface's getters. But, I'm obviously trying to force the Kotlin interface to fit this model and it is probably a noob Kotlin user mistake, as I ultimately have no access to super here.
From the class docs
In fact, for declaring properties and initializing them from the
primary constructor, Kotlin has a concise syntax:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
I seem to be getting errors that this hides members of the supertype and requires the override modifier. If I try to override the supertype and then provide a default, it complains that supertypes aren't accessible in this context.
Can this only be done in a secondary constructor with mutable
values?
I'm also open to suggestions/feedback on better ways to provide an interface that supports configuration over convention. The most important aspect being that the interface always provide defaults for a convention. Or, more simply put:
Is there a better way?
Please have a look at
https://kotlinlang.org/docs/reference/visibility-modifiers.html
Especially the 2nd paragraph around Classes and interfaces.
From these examples, a suggestion would be, if it's possible in your case, to use a base class instead of an interface. This would look something like this :
open class DefaultRepo {
protected open val name = "foo/bar/baz"
protected open val pattern = "foo/bar/baz"
protected open val url = "http://mycompanyrepo.com/$name"
open fun add() {
TODO("do something interesting here")
}
}
class RenamedRepo(newName: String): DefaultRepo() {
override val name = newName
}
class ConfigurableRepo(n: String, p: String, u: String): DefaultRepo() {
override val name = n
override val pattern = p
override val url = u
override fun add() {
}
}
Use an abstract class like this:
abstract class Repository {
open val name = "default"
open val pattern = "default"
open val url = "default"
}
// example: only override name
class RepoWithDifferentName(
override val name: String
): Repository() {
// ...
}
Since I now figured out how to do it with the vars, here an answer for the exact interface from your question.
class ConfigurableRepo(
private var _name: String,
private var _pattern: String,
private var _url: String) : Repository
{
override val name get () = _name
override val pattern get () = _pattern
override var url
get () = _url
set (u: String) { _url = u }
}
If your intention is to have default values for the parameters of the default constructor like this (pseudocode, which does not compile):
class ConfigurableRepo(
override var name,
override var pattern = super.pattern,
override var url = super.url
) {
...
}
Then the way to go would be the "null as default pattern" which one can always use when the desired default value can't be expressed in the method declaration. In your case, it could work out like this:
class ConfigurableRepo (
override val name: String,
_url: String? = null,
_pattern: String? = null
): Repository {
override val pattern = _pattern ?: super.pattern
override var url = _url ?: super.url
// ... more code ...
}
As you can see, the trick works with val as well as var properties and fields. Property name which does not have a default value in the interface is implemented with straight-forward field in this class.
If I want to modify one single parameter in a constructor.
In the Scala case class, the apply method will be overridden twice. Unless apply applies ( no pun ) to auxiliary constructor.
Related to
Modifying case class constructor parameter before setting value
How to override apply in a case class companion
How one can modify one single input from a constructor ?
Criteria :
The class must hold immutable data. All data must be accessible.
Note it doesn't have to be case class.
Additionally , no need to use the apply method.
no extra unused parameters. In the example below, _fistName is still accessible but unused.
case class Person(
lastName: String,
_fistName: String, ... )
{ lazy val fistName = _fistName.toLowerCase }
Here are two simple approaches.
Using class:
class Person(first: String, last: String) {
val firstName = first.toLowerCase
val lastName = last.toLowerCase()
}
val person = new Person("Adam", "Smith")
println(person.firstName + " " + person.lastName) // adam smith
Using trait + object's apply():
trait Person {
val firstName: String
val lastName: String
}
object Person {
def apply(first: String, last: String) = new Person {
override val firstName: String = first.toLowerCase
override val lastName: String = last.toLowerCase
}
}
val person = Person("Adam", "Smith")
println(person.firstName + " " + person.lastName) // adam smith
Note that classes must be instantiated with new, while traits (created with apply()) don't.
Why no case classes? Well, they are designed to serve as ADTs (abstract data types). Basically, they are thought of as containers for some data without any logic. That's why they get apply() out-of-the-box. If you want to override it, then means your class doesn't have the semantics of a case class.
I can see that #prayag took the effort of trying to help you force it into a case class, but seriously, if it doesn't have to be one (and you said so in the question), then don't make it one.
The reference you had posted seems to have lot of answers as well.
Two simple ways I could think of
make it abstract case class and define companion object which would mutate the value you want
define the member of case class as var and mutate it.
eg. (using scalatest)
class CaseClassSpecs extends FunSpec {
describe("case class modify") {
it("APPROACH 1 : modifies abstract case class member") {
object Item {
def apply(itemId: String, itemName: String) :Item = new Item(itemId.toLowerCase, itemName) {}
}
abstract case class Item private (val itemId: String, itemName: String)
val item1 = Item("SKU-ONE", "Shirts")
assert(item1.itemId == "sku-one")
assert(item1.itemName == "Shirts")
}
it("APPROACH 2 : modifies case class member which is var") {
case class Item (var itemId: String, itemName: String) {
itemId = itemId.toLowerCase()
}
val item1 = Item("SKU-ONE", "Shirts")
assert(item1.itemId == "sku-one")
assert(item1.itemName == "Shirts")
}
}
}
Class parameters are not necessarily class members. You can have class parameters that do not become class members.
Method 1
class Foo(bar0: String) {
val bar = bar0.toLowerCase()
}
#main
def main(): Unit = {
println(Foo("AsDfGh").bar)
}
prints:
asdfgh
and the decompiled code is:
public class Foo {
private final String bar;
public Foo(final String bar0) {
this.bar = bar0.toLowerCase();
}
public String bar() {
return this.bar;
}
}
You see, bar0 is a "temporary" value, it does not become a field because it is not referenced.
So if you want to change a value, just do not use the original value in the methods.
Method 2
For case classes, it does not seem to work in 2022, but here is another trick:
case class Point (x: Double, y: Double)
class PolarPoint(r: Double, alpha: Double) extends Point(r*Math.cos(alpha), r*Math.sin(alpha))
Here r and alpha do not become members of PolarPoint.
If you don't need two types, you can make the 1st constructor protected:
case class Foo protected(x:Int)
class FooImpl(x0:Int) extends Foo(myFunc(x0))
You will reference objects as Foos but create FooImpls.
Method 3
Your class can have multiple parameter lists and implicits:
class Qux(x:String)(implicit val y:String = x.toLowerCase())
is converted to:
public class Qux {
private final String y;
public static String $lessinit$greater$default$2(String var0) {
return Qux$.MODULE$.$lessinit$greater$default$2(var0);
}
public Qux(final String x, final String y) {
this.y = y;
}
public String y() {
return this.y;
}
}
You see that here only y becomes a field.
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.
Could someone explain me why:
abstract class Super(var title: String)
class Sub(title: String) extends Super(title) {
def test = println(title)
}
val s = new Sub("a")
s.test
s.title = "b"
s.test
prints:
a
a
instead of:
a
b
?
It's easy. You simply refers to constructor param, not the inherited variable. You may either rename constructor param, or refer to the var with this. prefix
class Sub(titleP: String) extends Super(titleP) {
def test = println(title)
}
Is it possible to use named arguments in a Scala constructor, and later on override getters and setters without breaking the constructor interface or making the code extremely ugly?
Take the following bit of scala code
class Person( var FirstName: String, var LastName: String )
Nice and clean. This would create a simple class called person, which we could use in the following way
val john = new Person( FirstName="John", LastName="Doe" )
john.FirstName = "Joe"
println( john.FirstName )
Later, we decide we want to add some validation to the FirstName setter. As such, we create a new private local variable and override the getter and setter methods
class Person( var _FirstName: String, var _LastName: String ) {
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
}
Still somewhat clean, however in order to do this, we've had to change the constructor argument names, thus breaking the external interface.
The first solution to this problem I came up with was
class Person {
var _FirstName:String = null
var LastName:String = null
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
def this( FirstName: String, LastName: String ){
this()
this._FirstName = FirstName
this.LastName = LastName
}
}
Which is somewhat ugly and inelegant, and removes most of the nice reasons I was using scala in the first place.
Is there a better way of doing this?
tl;dr How to override getters/setters for members defined in the default constructor without making the code ugly or changing the public interface?
Did you consider using an companion object?
class Person private (f: String, l: String ) {
var FirstName = f
var LastName = l
}
object Person {
def apply(FirstName:String, LastName:String) =
new Person(FirstName, LastName)
}
If you're not already using implicit conversions to create the arguments, you can do something like this:
def validateName(s: String) = {
if (s.length>0 && s(0).isUpper) s
else throw new IllegalArgumentException(s+" is not a name!")
}
object Example {
private[Example] class ValidatedName(val s: String) { }
class Person(var firstName: ValidatedName, var lastName: String) { }
implicit def string2valid(s: String) = new ValidatedName(validateName(s))
implicit def valid2string(v: ValidatedName) = v.s
}
scala> new Example.Person("Joe","Schmoe")
res17: Example.Person = Example$Person#51887dd5
scala> new Example.Person("ee","cummings")
java.lang.IllegalArgumentException: ee is not a name!
It's not binary compatible, but it is source compatible (again, if the names weren't already relying upon implicit conversions).
Another slightly longer possibility is to create a stealth ancestor:
class CheckedPerson(private var first: String, var lastName: String) {
def firstName = first
def firstName_=(s: String) { first = validateName(s) }
}
class Person(firstName: String, lastName: String) extends
CheckedPerson(validateName(firstName),lastName) { }
for which I'm not sure about binary compatibility, but will definitely give source compatibility.
No. There is currently no way to do that, it's currently not the focus of research.
It is one of my major pet peeves I have with the language: There is no sensible way to combine constructor arguments and self-defined getter/setter methods.
If you're not happy with the functionality class Person( var FirstName: String, var LastName: String ) provides, it basically means "back to Java's verboseness".