I want to create a class generator (for Avro models). I have a problem because sometime the field of the class I generate could be one of many different types, let's say it could be an Int
or a String
or whatever. The simple idea is to create that field with the type Any
and check at runtime that it is ok.
The problem is that using Any
discard some of the powerfull features of the scala type system. Some bug in the code will not be caught at compile time (if I give a List
to a function expecting a String
or Int
covered by an Any
).
For example :
Let's I have this description of my class :
{"className" : "MyClass", "fields" : [
{ "fieldName" : "myField", "type" : ["String", "Int"]}
]}
From this description, I create this class :
class MyClass(myField: Any)
I want to create something like this :
class MyClass(myField: String or Int)
Should I stop using Any
? Is using Any
generally considered a good idea in the scala community ?
typing. Union. Union type; Union[X, Y] is equivalent to X | Y and means either X or Y. To define a union, use e.g. Union[int, str] or the shorthand int | str .
Union types are used when a value can be more than a single type. Such as when a property would be string or number .
To check if a string is in a union type:Create a reusable function that takes a string as a parameter. Add the values of the union type of an array. Use the includes() method to check if the string is contained in the array.
In TypeScript, we can define a variable which can have multiple types of values. In other words, TypeScript can combine one or two different types of data (i.e., number, string, etc.) in a single type, which is called a union type.
Is using Any generally considered a good idea in the scala community?
Nope. Any
means no type information, so it's generally considered a bad practice.
In Scala you can express a union type using Either
, although it gets cumbersome if you have a lot of possible types in the union. Example:
class MyClass(myField: Either[String, Int]) {
def doSomething = myField match {
case Left(myStringField) => ???
case Right(myIntField) => ???
}
}
Another viable approach would be to make MyClass
generic in its type:
class MyClass[A](myField: A)
However this is not setting any constraint on the type of A
.
In order to place a constraint, e.g. make it a finite subset of types, you can use ad-hoc polymorphism:
trait MyConstraint[A]
class MyClass[A: MyConstraint](myField: A)
Now new MyClass(myValue)
won't compile unless there is an implicit MyConstraint[A]
in scope. Now you can whitelist the types you want to allow using implicit values
implicit object IntConstraint extends MyConstraint[Int]
implicit object StringConstraint extends MyConstraint[String]
Example:
new MyClass(42) // ok, there's implicit evidence of MyConstraint[Int]
new MyClass("foo") // ok, there's implicit evidence of MyConstraint[String]
new MyClass(false) // won't compile, no implicit evidence of MyConstraint[Boolean]
In technical terms, MyConstraint
is a type class, used to refine the type A
in the constructor of MyClass
.
You can characterize a type class even further, by requiring that a set of operations are defined for each of its instances. E.g.
trait MyConstraint[A] {
def mandatoryOp: A
}
implicit object IntConstraint extends MyConstraint[Int] {
def mandatoryOp = 42
}
implicit object StringConstraint extends MyConstraint[String] {
def mandatoryOp = "foo"
}
class MyClass[A](myField: A)(implicit ev: MyConstraint[A]) {
def doSomething: A = ev.mandatoryOp
}
Please note that A: MyConstraint
is just syntactic sugar for requiring an implicit parameter of type MyConstraint[A]
. In the last example I chose the explicit syntax in order to have the implicit parameter ev
available in scope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With