Having just started to dip my toes with Scala and am now looking to understand whether there is any way to express Deep partial types (recursive types) in Scala?
###################### Update ######################
What I am trying to achieve is to take an existing case class and somehow "Version" it, such that it recursively adds that to every field even at deeply nested levels
e.g. lets assume I have these classes defined
case class A(p1: Int, p2: Int, ..)
case class B(p1: Map[String, A])
case class C(param1: String, param2: String, param3: Option[B])
I want to version C, which means for every parameter type (recursively) I want an optional version: Int to be added.
The naive way, I see, would be to define a new class VersionedValue and then manually redefine all of the existing types to accommodate this.
e.g. what it may look like
case class VersionedValue [A](value: A, version: Option[Int])
case class A(p1: VersionedValue[Int], p2: VersionedValue[Int])
case class B(p1: Map[String, VersionedValue[A]])
case class C(p1: VersionedValue[String],
p2: VersionedValue[String],
p3: Option[B])
####################################################
Having worked with TypeScript for a while, that usually would look something like this:
/**
* the VersionedDeepPartial<T> will recursively iterate through
* each of the "keys" (or attributes) and for each creates a new
* object that adds a version field. The value attribute will hold
* what was originally the type for the key.
*/
export type VersionedDeepPartial<T> = {
[P in keyof T]?: {value: VersionedDeepPartial<T[P]>; version?:number}
};
// So if I had an interface (in typescript) looking like:
interface A {
p1: number,
p2: number,
}
interface B {
[key: string]: A
}
interface C {
p1: String,
p2: String,
p3: B
}
/**
* Note that interface C does not define any versioning attribute to
* its fields. But by wrapping the type within the
*/
VersionedDeepPartial<T>
const partialFoo: VersionedDeepPartial<C> = {
p1: { value: "asdf", version: 1 },
p2: { value: "asdf" },
p3: {
"somekey": {
p1: {value: 1, version: 1},
p2: {value: 2}
}
}
Basically, what I want is to define a new type by recursing through an existing type/class definition and "injecting" versioning to it.
I've read/browsed around for explanations on this topic with no success. Is this possible to express in Scala?
You maybe want Higher Kinded Data approach:
case class HKDA[F[_]](p1: F[Int], p2: F[Int], ..)
case class HKDB[F[_]](p1: Map[String, HKDA[F]])
case class HKDC[F[_]](param1: F[String], param2: F[String], param3: Option[HKDB[F]])
type Id[A] = A
type A = HKDA[Id]
type B = HKDB[Id]
type C = HKDC[Id]
case class Versioned[A](a: A, version: Int)
type VersionedA = HKDA[Versioned]
type VersionedB = HKDB[Versioned]
type VersionedC = HKDC[Versioned]
Related
I'm sorry for the vague or possibly incorrect question, but I'm not even sure how to state my problem in one sentence.
I have 2 traits:
trait Property[T] {
val name: String
def conformsTo(rule: Rule[T]): Boolean
}
and
trait Rule[T] {
def evaluate(valueToCheck: T): Boolean
}
I have a list of concrete classes implementing Property and a map of Rule implementations, where both parameterized types can either be a Double or a String.
When traversing this list, for each Property instance I pass in a concrete Rule implementation to the conformsTo method, the compiler however errors with a type mismatch.
So in a broader sense, what I'm trying to achieve is to have a list of attributes that can be evaluated based on a given set of rules of different types, without splitting these attributes into separate lists by type.
I tried tinkering with upper/lower bounds and implicit types but ended up with nothing.
The complete code:
trait Rule[T] {
def evaluate(valueToCheck: T): Boolean
}
case class GreaterThan(value: Double) extends Rule[Double] {
override def evaluate(valueToCheck: Double): Boolean = {
valueToCheck > this.value
}
}
case class LessThan(value: Double) extends Rule[Double] {
override def evaluate(valueToCheck: Double): Boolean = {
valueToCheck < this.value
}
}
case class Is(value: String) extends Rule[String] {
override def evaluate(valueToCheck: String): Boolean = {
valueToCheck == this.value
}
}
case class isNot(value: String) extends Rule[String] {
override def evaluate(valueToCheck: String): Boolean = {
valueToCheck != this.value
}
}
trait Property[T] {
val name: String
def conformsTo(rule: Rule[T]): Boolean
}
case class DoubleProperty(name: String, value: Double) extends Property[Double] {
override def conformsTo(rule: Rule[Double]): Boolean = rule.evaluate(value)
}
case class StringProperty(name: String, value: String) extends Property[String] {
override def conformsTo(rule: Rule[String]): Boolean = rule.evaluate(value)
}
object Evaluator extends App {
val ruleMap = Map(
"name1" -> GreaterThan(123),
"name1" -> LessThan(500),
"name2" -> GreaterThan(1000),
"name3" -> Is("something"))
val numericProperty = DoubleProperty("name1", 600)
val numericProperty2 = DoubleProperty("name2", 1000)
val stringProperty = StringProperty("name3", "something")
val stringProperty2 = StringProperty("name4", "something")
val attributes = List(
numericProperty,
numericProperty2,
stringProperty,
stringProperty2)
val nonConforming = attributes
.filter(x => ruleMap.contains(x.name))
.filter(x => !x.conformsTo(ruleMap(x.name))).toList
}
the error:
type mismatch;
found : Product with Rule[_ >: String with Double] with java.io.Serializable
required: Rule[(some other)<root>._1]
Note: Any >: _1 (and Product with Rule[_ >: String with Double] with java.io.Serializable <: Rule[_ >: String with Double]), but trait Rule is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
.filter(x => !x.conformsTo(ruleMap(x.name))).toList
Thank you for any help.
Firstly, based on error message it can make sense to extend Rule from Product with Serializable
https://typelevel.org/blog/2018/05/09/product-with-serializable.html
Then error message changes to:
type mismatch;
found : App.Rule[_1(in value $anonfun)] where type _1(in value $anonfun) >: String with Double
required: App.Rule[<root>._1]
Secondly, you can make Rule contravariant
trait Rule[-T] extends Product with Serializable {
def evaluate(valueToCheck: T): Boolean
}
type mismatch;
found : App.Rule[String with Double]
required: App.Rule[_1]
and Property covariant
trait Property[+T] {
val name: String
def conformsTo(rule: Rule[T]): Boolean
}
type mismatch;
found : App.Rule[String with Double]
required: App.Rule[Any]
The thing is that this not gonna work with List. Elements of attributes have types Property[Double] and Property[String]. But when you add them to a list they lost their individual types and become just Property[Any] (Property[Double | String] if there were union types in Scala 2).
Similarly, values of ruleMap have types Rule[Double] and Rule[String]. But when you put them with some keys into a map they lost their individual types and become just Rule[Double with String] (or even Rule[Nothing]).
If you didn't modify variances of Property and Rule then the types would be Property[_] and Rule[_] (actually, Property[_1] and Rule[_2]) where type parameters don't correspond to each other.
But the signature of conformsTo says that property.conformsTo(rule) compiles only when arguments have types Property[T] and Rule[T] for the same T, which you can't guarantee.
You could try to use HList instead of List and HMap instead of Map in order to keep individual types of elements. But then in order to make filter work you should know whether ruleMap.contains(x.name) at compile time. So you would have to make keys have singleton types and trait Property to depend on this one more type parameter trait Property[+T, S <: String with Singleton] { val name: S ...
Why Does This Type Constraint Fail for List[Seq[AnyVal or String]]
Scala: verify class parameter is not instanceOf a trait at compile time
How to make a typeclass works with an heterogenous List in scala
Use the lowest subtype in a typeclass?
flatMap with Shapeless yield FlatMapper not found
The easiest way to make your code compile is
val nonConforming = attributes
.filter(x => ruleMap.contains(x.name))
.filter{
case x: Property[t] => !x.conformsTo(ruleMap(x.name).asInstanceOf[Rule[t]])
}
Is it possible to have constructor parameters that don't become fields? For example,
class Foo(p1: String, p2: Int) {
val f1 = p1 * p2
}
The only field I want is f1, not p1 and p2. The idea is to derive fields from parameters.
This is good as it is - both p1 and p2 aren't fields here, just f1. Making them fields is opt-in rather than opt-out (except for case classes, where all parameters are also fields, and you can't do anything about it).
To make them fields, you'd have to add var or val, something like
class Foo(val p1: String, var p2: Int)
EDIT:
In case you want a field with the same name as a parameter but a different type or something, you can do something like this:
class Foo(p1: String, val p2: Int) {
//Can be val if you want
var _p1: Int = (p1 : String).toInt
def p1: Int = _p1
def p1_=(p1: Int) = _p1 = p1 //An optional setter
}
This is practically the same as having a field called p1.
If you also want to set fields using some complex operations and use local variables, you can use blocks (and maybe an auxiliary constructor)
class Foo(p1: String, val p2: Int) {
val _p1: Int = {
val intermediateVariable: String = p1
intermediateVariable.toInt
}
def p1: Int = _p1
or
class Foo(val p1: Int) {
def this(p1: String) = this({
val uselessIntermediateVariable = p1.toInt
uselessIntermediateVariable
})
}
In a simple class, if you don't add any modifier as val or var, the parameters of the constructor are private elements. Example:
class C(val a: Int, b: Int) {
val c = a + b
}
val i = new C(1,2)
println(i.a) // it's public
println(i.b) // this line won't allow you to compile, it's private
println(i.c) // also public
The exception is made is you make a case class, with case modifier all the parameters of the constructor will be public. You can make them private marking as private the parameter. Example:
case class C(a: Int, private val b: Int) {
val c = a + b
}
val i = new C(1,2)
println(i.a) // it's public
println(i.b) // this line won't allow you to compile, it's private
println(i.c) // also public
Just to clarify the existing answers, non-case class parameters can automatically become fields, but only if they are used in methods or lazy val initializers, e.g.
class Foo(p1: String, p2: Int) {
val f1 = p1 * p2
def m1() = p1 + p1
}
will make p1 a field. There's no way to avoid it other than not using them in such a way.
I have different sources and corresponding parameters
Source1, Source2, Source3
Parameter1, Parameter2, Parameter3,
Source: is trait (can be changed)
trait Source[T] {
def get(Parameter)(implicit c: Context): MyData[T]
}
Parameter is also a trait
trait Parameter
I have different OutputType class: T1, T2, T3
I need output as: MyData[OutputType]
Fixed API signature (changes to the signature not quite preferable):
val data1: MyData[T1] = MyAPI.get[T1](Parameter1("a", "b")) // this should give MyData from Source1 of type T1
val data2: MyData[T2] = MyAPI.get[T2](Parameter3(123)) // this should give MyData from Source3 of type T2
Some source supports some output types (say T1, T2), but some may not.
What I did:
I tried using scala reflection typeTag to determine the type at runtime, but since return type will be MyData[T], and is in contra-variant position, it wont know the actual return type. (Why does TypeTag not work for return types?)
e.g.
object MyAPI {
get[T: TypeTag](p: Parameter)(implicit c: Context): MyData[T] = {}
}
I also tried using type-class pattern. Scala TypeTag Reflection returning type T
I can work with different OutputType creating implicit val for each, but would only work for single Source1. I can't manage to work for all sources.
I was trying to do:
object MyAPI {
get[T: SomeConverter](p: Parameter)(implicit c: Context): MyData[T] = {
p match {
case Parameter1 => Source1[T].read(p.asInstanceOf(Parameter1)
case Parameter2 => Source2[T].read(p.asInstanceOf(Parameter2)
}
}
}
Disclaimer: I think I figured out what you want. I'm also learning to design type-safe APIs, so here's one.
Provided variant uses implicits. You have to manually establish mapping between parameter types and results they yield, which may or may not include sources. It does not work on runtime, however, so I also removed common trait Parameter. It also does not impose any restrictions on the Sources at all.
It also "looks" the way you wanted it to look, but it's not exactly that.
case class User(id: Int) // Example result type
// Notice I totally removed any and all relation between different parameter types and sources
// We will rebuild those relations later using implicits
object Param1
case class Param2(id: Int)
case class Param3(key: String, filter: Option[String])
// these objects have kinda different APIs. We will unify them.
// I'm not using MyData[T] because it's completely irrelevant. Types here are Int, User and String
object Source1 {
def getInt = 42
}
object Source2 {
def addFoo(id: Int): Int = id + 0xF00
def getUser(id: Int) = User(id)
}
object Source3 {
def getGoodInt = 0xC0FFEE
}
// Finally, our dark implicit magic starts
// This type will provide a way to give requested result for provided parameter
// and sealedness will prevent user from adding more sources - remove if not needed
sealed trait CanGive[Param, Result] {
def apply(p: Param): Result
}
// Scala will look for implicit CanGive-s in companion object
object CanGive {
private def wrap[P, R](fn: P => R): P CanGive R =
new (P CanGive R) {
override def apply(p: P): R = fn(p)
}
// there three show how you can pass your Context here. I'm using DummyImplicits here as placeholders
implicit def param1ToInt(implicit source: DummyImplicit): CanGive[Param1.type, Int] =
wrap((p: Param1.type) => Source1.getInt)
implicit def param2ToInt(implicit source: DummyImplicit): CanGive[Param2, Int] =
wrap((p: Param2) => Source2.addFoo(p.id))
implicit def param2ToUser(implicit source: DummyImplicit): CanGive[Param2, User] =
wrap((p: Param2) => Source2.getUser(p.id))
implicit val param3ToInt: CanGive[Param3, Int] = wrap((p: Param3) => Source3.getGoodInt)
// This one is completely ad-hoc and doesn't even use the Source3, only parameter
implicit val param3ToString: CanGive[Param3, String] = wrap((p: Param3) => p.filter.map(p.key + ":" + _).getOrElse(p.key))
}
object MyApi {
// We need a get method with two generic parameters: Result type and Parameter type
// We can "curry" type parameters using intermediate class and give it syntax of a function
// by implementing apply method
def get[T] = new _GetImpl[T]
class _GetImpl[Result] {
def apply[Param](p: Param)(implicit ev: Param CanGive Result): Result = ev(p)
}
}
MyApi.get[Int](Param1) // 42: Int
MyApi.get[Int](Param2(5)) // 3845: Int
MyApi.get[User](Param2(1)) // User(1): User
MyApi.get[Int](Param3("Foo", None)) // 12648430: Int
MyApi.get[String](Param3("Text", Some(" read it"))) // Text: read it: String
// The following block doesn't compile
//MyApi.get[User](Param1) // Implicit not found
//MyApi.get[String](Param1) // Implicit not found
//MyApi.get[User](Param3("Slevin", None)) // Implicit not found
//MyApi.get[System](Param2(1)) // Same. Unrelated requested types won't work either
object Main extends App {
sealed trait Parameter
case class Parameter1(n: Int) extends Parameter with Source[Int] {
override def get(p: Parameter): MyData[Int] = MyData(n)
}
case class Parameter2(s: String) extends Parameter with Source[String] {
override def get(p: Parameter): MyData[String] = MyData(s)
}
case class MyData[T](t: T)
trait Source[T] {
def get(p: Parameter): MyData[T]
}
object MyAPI {
def get[T](p: Parameter with Source[T]): MyData[T] = p match {
case p1: Parameter1 => p1.get(p)
case p2: Parameter2 => p2.get(p)
}
}
val data1: MyData[Int] = MyAPI.get(Parameter1(15)) // this should give MyData from Source1 of type T1
val data2: MyData[String] = MyAPI.get(Parameter2("Hello World")) // this should give MyData from Source3 of type T2
println(data1)
println(data2)
}
Does this do what you want?
ScalaFiddle: https://scalafiddle.io/sf/FrjJz75/0
I want to write a generic function functionChooser which will choose which function to use from a few options, based on a String argument.
This works:
def a (arg: String) = arg + " with a"
def b (arg: String) = arg + " with b"
def c (arg: String) = arg + " with c"
def functionChooser(func: String, additionalArg: String) = {
val f = func match {
case "a" => a _
case "b" => b _
case _ => c _
}
f(additionalArg)
}
scala> functionChooser("a", "foo")
res18: String = foo with a
I'm having trouble in making functionChooser generic, e.g. when functions a, b, and c return different case classes:
case class A(s: String)
case class B(s: String)
case class C(s: String)
def a (arg: String) = A(arg)
def b (arg: String) = B(arg)
def c (arg: String) = C(arg)
//functionChooser def as before
scala> functionChooser("a", "foo")
res19: Product with Serializable = A(foo)
I don't quite understand what I got there, I know I get an error when calling functionChooser("a", "foo").s ("error: value s is not a member of Product with Serializable").
Lastly, what I really want is that the functions would return Lists of these case classes, e.g.:
def a (arg: String) = List(A(arg))
def b (arg: String) = List(B(arg))
def c (arg: String) = List(C(arg))
So functionChooser should be generic to List[T] where T is some class.
The function functionChooser will return the most specific common super type of the case classes A, B and C. Since case classes inherit from Product and Serializable, the common super type is Product with Serializable.
If you want to access the case class field s you either have to cast the result, via pattern matching, or you provide a common base class of all your classes A, B and C which allows you to access the field.
trait Base {
def s: String
}
case class A(s: String) extends Base
case class B(s: String) extends Base
case class C(s: String) extends Base
With this type definition the return type of functionChooser would be Product with Serializable with Base and, thus, the result would allow you to access s.
If your function a, b and c return a List of the respective case class, then the return type of functionChooser would be List[Product with Serializable with Base].
Update
If you cannot change the class hierarchy, then you either have to cast the result or you could extract the necessary information in the functionChooser and wrap it in another type which contains the super set of all data you need. E.g.
def functionChooser(func: String, additionalArg: String): String = {
val f = func match {
case "a" => (a _).s
case "b" => (b _).s
case _ => (c _).s
}
f(additionalArg)
}
Note: Here I only extract the field s which is the super set of all required information.
You should return the upper common type for all three functions. Object (AnyRef) always fit.
def functionChooser(func: String, additionalArg: String) : AnyRef = {
In your case, where all possible returning values are Lists you may use more specific type:
def functionChooser(func: String, additionalArg: String) : List[_] = {
Of course that will eliminate type information. Any method should return the same type, it could not be polymorphic on it. So, you need to use .asInstanceOf[T] case further, to get this information back.
That make sense, because the actual type is unknown during runtime. e.g. the dispatcher string may be entered by user. If it would be known during compile time, then you could just use appropriate method without referring to descriptive string.
If you would like to get some common behaviour for all possible return types, than you should define a common trait for them and place common methods to it.
I have a lot of similar case classes which mean different things but have the same argument list.
object User {
case class Create(userName:String, firstName: String, lastName: String)
case class Created(userName:String, firstName: String, lastName: String)
}
object Group {
case class Create(groupName:String, members: Int)
case class Created(groupName:String, members: Int)
}
Given this kind of a setup, I was tired of writing methods that take an argument of type Create and return an argument of type Created. I have tons of test cases that do exactly this kind of thing.
I could write a function to convert one case class into the other. This function converts User.Create into User.Created
def userCreated(create: User.Create) = User.Create.unapply(create).map((User.Created.apply _).tupled).getOrElse(sys.error(s"User creation failed: $create"))
I had to write another such function for Group.
What I'd really like to have is a generic function that takes the two types of the case classes and an object of one case class and converts into the other. Something like,
def transform[A,B](a: A):B
Also, this function shouldn't defeat the purpose of reducing boilerplate. Please feel free to suggest a different signature for the function if that's easier to use.
Shapeless to the rescue!
You can use Shapeless's Generic to create generic representations of case classes, that can then be used to accomplish what you're trying to do. Using LabelledGeneric we can enforce both types and parameter names.
import shapeless._
case class Create(userName: String, firstName: String, lastName: String)
case class Created(userName: String, firstName: String, lastName: String)
case class SortOfCreated(screenName: String, firstName: String, lastName: String)
val c = Create("username", "firstname", "lastname")
val createGen = LabelledGeneric[Create]
val createdGen = LabelledGeneric[Created]
val sortOfCreatedGen = LabelledGeneric[SortOfCreated]
val created: Created = createdGen.from(createGen.to(c))
sortOfCreatedGen.from(createGen.to(c)) // fails to compile
For the record, here is the simplest typesafe syntax I've managed to implement:
implicit class Convert[A, RA](value: A)(implicit ga: Generic.Aux[A, RA]) {
def convertTo[B, RB](gb: Generic.Aux[B, RB])(implicit ev: RA =:= RB) =
gb.from(ga.to(value))
}
And it can be used like this:
case class Create(userName: String, firstName: String, lastName: String)
case class Created(userName: String, firstName: String, lastName: String)
val created = Create("foo", "bar", "baz").convertTo(Generic[Created])
Or the same thing with LabelledGeneric to achieve better type safety:
implicit class Convert[A, RA](value: A)(implicit ga: LabelledGeneric.Aux[A, RA]) {
def convertTo[B, RB](gb: LabelledGeneric.Aux[B, RB])(implicit ev: RA =:= RB) =
gb.from(ga.to(value))
}
val created = Create("foo", "bar", "baz").convertTo(LabelledGeneric[Created]))