I am trying to create a Request object where I can get a single String as input or a list of String as Input. I tried to look through various answers but I don't think I have found any use of List in constructor.
I am trying to do something like this.
class GetRequest(val url: String) {
def this(val urlList: List[String]){
}
}
Is it something to do with List's immutability?
Scala enforces to have one main constructor and as much as you need
auxiliary constructors. There's a rule: each constructor must invoke one of the previously defined constructors.
This should work for you:
class GetRequest(val urlList: List[String]) {
def this(url: String) {
this(List(url))
}
}
An auxiliary constructor isn't really necessary when making a distinction between a single passed parameter and a collection of the same type. That's what the varargs syntax is for.
class GetRequest(val urls: String*)
new GetRequest("s")
new GetRequest("a","b","c")
new GetRequest(List("x","y","z"):_*)
In the constructor code urls can be treated as a Seq[String] of zero or more elements.
Related
I would like to create a macro that generates a secondary constructor{'s body). Is it possible to do this without resorting to macro annotations? (i.e. macro-paradise plugin)
For example:
Something like this:
class A(a : String, b : String) {
def this(s : List[Any]) = macro fromlist
}
Should be equivalent to something like this:
class A(a : String, b : String) {
def this(s : List[Any]) = this(s.head.toString, s.tail.head.toString)
}
Simply using the "macro" keyword does not seem to help. Is this completely disallowed in plain Scala? Thanks.
The problem is, that a constructor is not a method returning a new instance, but a method initializing an already created one. (So the = in your constructor definition does not make sense, the parent constructor does not return anything).
The next problem is, that an alternative constructor in Scala has to call an other constructor as the first step, you cannot call something else, not even a macro.
You could however call a macro to generate the parameters to this, like
this(fromList(s): _*)
But why would you even want to do that? It is very uncommon in Scala to have multiple constructors. The common way is to have an overloaded apply method in the companion object. You don't have any restrictions there.
I have this case class with a lot of parameters:
case class Document(id:String, title:String, ...12 more params.. , keywords: Seq[String])
For certain parameters, I need to do some string cleanup (trim, etc) before creating the object.
I know I could add a companion object with an apply function, but the LAST thing I want is to write the list of parameters TWICE in my code (case class constructor and companion object's apply).
Does Scala provide anything to help me on this?
My general recommendations would be:
Your goal (data preprocessing) is the perfect use case of a companion object -- so it is maybe the most idiomatic solution despite the boilerplate.
If the number of case class parameters is high the builder pattern definitely helps, since you do not have to remember the order of the parameters and your IDE can help you with calling the builder member functions. Using named arguments for the case class constructor allows you to use a random argument order as well but, to my knowledge, there is not IDE autocompletion for named arguments => makes a builder class slightly more convenient. However using a builder class raises the question of how to deal with enforcing the specification of certain arguments -- the simple solution may cause runtime errors; the type-safe solution is a bit more verbose. In this regard a case class with default arguments is more elegant.
There is also this solution: Introduce an additional flag preprocessed with a default argument of false. Whenever you want to use an instance val d: Document, you call d.preprocess() implemented via the case class copy method (to avoid ever typing all your arguments again):
case class Document(id: String, title: String, keywords: Seq[String], preprocessed: Boolean = false) {
def preprocess() = if (preprocessed) this else {
this.copy(title = title.trim, preprocessed = true) // or whatever you want to do
}
}
But: You cannot prevent a client to initialize preprocessed set to true.
Another option would be to make some of your parameters a private val and expose the corresponding getter for the preprocessed data:
case class Document(id: String, title: String, private val _keywords: Seq[String]) {
val keywords = _keywords.map(kw => kw.trim)
}
But: Pattern matching and the default toString implementation will not give you quite what you want...
After changing context for half an hour, I looked at this problem with fresh eyes and came up with this:
case class Document(id: String, title: String, var keywords: Seq[String]) {
keywords = keywords.map(kw => kw.trim)
}
I simply make the argument mutable adding var and cleanup data in the class body.
Ok I know, my data is not immutable anymore and Martin Odersky will probably kill a kitten after seeing this, but hey.. I managed to do what I want adding 3 characters. I call this a win :)
E.g.:
def updateAsinRecords(asins:Seq[String], recordType:String)
Above method takes a Seq of ASINs and a record type. Both have type of String. There are also other values that are passed around with type String in the application. Needless to say, this being Scala, I'd like to use the type system to my advantage. How to pass around string values in a type safe manner (like below)?
def updateAsinRecords(asins:Seq[ASIN], recordType:RecordType)
^ ^
I can imagine, having something like this:
trait ASIN { val value:String }
but I'm wondering if there's a better approach...
There is an excellent bit of new Scala functionality know as Value Classes and Universal Traits. They impose no runtime overhead but you can use them to work in a type safe manner:
class AnsiString(val inner: String) extends AnyVal
class Record(val inner: String) extends AnyVal
def updateAnsiRecords(ansi: Seq[AnsiString], record: Record)
They were created specifically for this purpose.
You could add thin wrappers with case classes:
case class ASIN(asin: String)
case class RecordType(recordType: String)
def updateAsinRecords(asins: Seq[ASIN], recordType: RecordType) = ???
updateAsinRecords(Vector(ASIN("a"), ASIN("b")), RecordType("c"))
This will not only make your code safer, but it will also make it much easier to read! The other big advantage of this approach is that refactoring later will be much easier. For example, if you decide later that an ASIN should have two fields instead of just one, then you just update the ASIN class definition instead of every place it's used. Likewise, you can do things like add methods to these types whenever you decide you need them.
In addition to the suggestions about using a Value Class / extends AnyVal, you should probably control the construction to allow only valid instances, since presumably not any old string is a valid ASIN. (And... is that an Amazon thing? It rings a bell somehow.)
The best way to do this is to make the constructor private and put a validating factory method in a companion object. The reason for this is that throwing exceptions in constructors (when an attempt is made to instantiate with an invalid argument) can lead to puzzling failure modes (I often see it manifest as a NoClassDefFoundError error when trying to load a different class).
So, in addition to:
case class ASIN private (asin: String) extends AnyVal { /* other stuff */ }
You should include something like this:
object A {
import scala.util.{Try, Success, Failure}
def fromString(str: String): Try[ASIN] =
if (validASIN(str))
Success(new ASIN(str))
else
Failure(new InvalidArgumentException(s"Invalid ASIN string: $str")
}
How about a type alias?
type ASIN = String
def update(asins: Seq[ASIN])
There doesn't seem to be a class called Tuple in the package, only the Tuple_ for effective access. If I want to take a tuple into constructor as a parameter what do I do?
class DataElement( datatype: Datatype, values: () ) extends Element {
This doesn't seem to work
All tuple classes implement Product, so you could use that.
I'd like to write a type alias to shorten, nice and encapsulated Scala code.
Suppose I got some collection which has the property of being a list of maps, the value of which are tuples.
My type would write something like List[Map[Int, (String, String)]], or anything more generic as my application allows it. I could imagine having a supertype asking for a Seq[MapLike[Int, Any]] or whatever floats my boat, with concrete subclasses being more specific.
I'd then want to write an alias for this long type.
class ConcreteClass {
type DataType = List[Map[Int, (String, String)]]
...
}
I would then happily use ConcreteClass#DataType everywhere I can take one, and use it.
Now suppose I add a function
def foo(a : DataType) { ... }
And I want to call it from outside with an empty list.
I can call foo(List()), but when I want to change my underlying type to be another type of Seq, I'll have to come back and change this code too. Besides, it's not very explicit this empty list is intended as a DataType. And the companion object does not have the associated List methods, so I can't call DataType(), or DataType.empty. It's gonna be even more annoying when I need non-empty lists since I'll have to write out a significant part of this long type.
Is there any way I can ask Scala to understand my type as the same thing, including companion object with its creator methods, in the interest of shortening code and blackboxing it ?
Or, any reason why I should not be doing this in the first place ?
The answer was actually quite simple:
class ConcreteClass {
type DataType = List[String]
}
object ConcreteClass {
val DataType = List
}
val d = ConcreteClass.DataType.empty
This enables my code to call ConcreteClass.DataType to construct lists with all the methods in List and little effort.
Thanks a lot to Oleg for the insight. His answer is also best in case you want not to delegate to List any call to ConcreteClass.DataType, but control precisely what you want to allow callers to do.
What about this?
class ConcreteClass {
type DataType = List[String]
}
object DataType {
def apply(): ConcreteClass#DataType = Nil
}
//...
val a = DataType()