I wonder if there is any better solution to my Problem.
I have a Service1 that handles two different input types (A|B) using method overloading.
A Service2 should provide a method that accepts all input types of Service1 and does some work with the result, which I do not want to duplicate.
I came up with two solutions, but neither of those is convincing to me.
data class A(...)
data class B(...)
class C
class Service1 {
fun doSomething(input: A): C {}
fun doSomething(input: B): C {}
}
// Solution 1
class Service2_v1 {
private val service: Service1
fun <T>doThings(input: T) {
val result = when(input) {
is A -> service.doSomething(input)
is B -> service.doSomething(input)
else -> throw IllegalArgumentException()
}
...work with result
}
}
// Solution2
class Service2_v2 {
private val service: Service1
fun doThings(input: A) = handleResult(service.doSomething(input))
fun doThings(input: B) = handleResult(service.doSomething(input))
private fun handleResult(result: C) {
...work with result
}
}
Since this only needs to work with Types that are known at compile type, I wonder if there is a cleaner solution with generics, similar to this pseudo code:
class Service2_notWorking {
private val service: Service1
fun <T : ArgumentTypeOf<Service1.doSometing>>doThings(input: T) {
val result = service.doSomething(input)
...work with result
}
}
Is there a way to do the following?:
class Someotherthing
class SomeotherthingDTO
class Something
class SomethingDTO
fun convert(entity: Someotherthing): SomeotherthingDTO = SomeotherthingDTO()
fun convert(entity: Something): SomethingDTO = SomethingDTO()
fun <T, D> generic(entity: T): D {
// TODO: check here if there is convert() that accepts type T?! somehow? reflection? reification? or it will be possible only in the future by using typeclasses (KEEP-87)?
return convert(entity)
}
fun main() {
val x: SomethingDTO = convert(Something())
println(x.toString())
}
Currently, the result is: none of the following can be called with the arguments supplied...
You'll need multiple receviers for this to work (KEEP-87). With those you'll be able to "find" the receivers properly.
Until then what I usually do is to put Converters in a ConverterRegistry to do the conversion like this:
interface Converter<A, B> {
val fromClass: KClass<A>
val toClass: KClass<B>
fun convert(from: A): B
fun convertBack(from: B): A
}
interface ConverterRegistry {
fun <A, B> tryConvert(from: KClass<A>, to: KClass<B>): B?
}
I´m working on a code with generics and when I use an in I got a TypeMismatch when compiling.
The code is the following:
open class A
class B:A()
data class DataContainer(val a:String,
val b:A)
interface Repo<T:A>{
fun setParam(param:T)
fun getParam():T
}
abstract class RepoImp<T:A>:Repo<T>{
private lateinit var parameter:T
override fun setParam(param: T) {
parameter = param
}
override fun getParam(): T {
return parameter
}
}
class BRepo:RepoImp<B>()
class Repo2(val repo: Repo<in A>){
fun process(b:DataContainer){
repo.setParam(b.b)
}
}
val repoB = BRepo()
val repo2 = Repo2(repoB)// Here I got: Type mismatch: inferred type is BRepo but Repo<in A> was expected
I also tried changing the attribute repo from Repo2 to Repo<*>
Since BRepo is a Repo<B>, it is not a Repo<in A>, (but it would satisfy Repo<out A>).
In other words, a Repo<in A> must be able to accept setParam(A()), but BRepo.setParam() can only accept a B or subclass of B.
Or to put it another way, BRepo is a Repo<B>, which is a tighter restriction on the type than Repo<A> when it comes to writing values (but looser restriction when reading values).
The reason class Repo2(val repo: Repo<*>) doesn't work is that Repo<*> is essentially a Repo<in Nothing/out A>. You can't call setParam() on a Repo<*> with any kind of object.
There's a design flaw in your code that you can't fix simply by changing Repo2's constructor signature. As it stands now, Repo2 needs to be able write A's to the object you pass to it, and a BRepo by definition does not support writing A's, only B's. You will need to make at least one of your class's definitions more flexible about types.
It might be easier to understand the covariance limitation with more common classes:
val stringList: MutableList<String> = ArrayList()
var anyList: MutableList<in Any> = ArrayList()
anyList.add(5) // ok
anyList = stringList // Compiler error.
// You wouldn't be able to call add(5) on an ArrayList<String>
Basically MutableList<String> is not a MutableList<in Any> the same way Repo<B> is not a Repo<in A>.
The Repo2 class expect to consume only type A, use Repo2<T : A>(val repo: Repo<in T>)
open class A
class B : A()
class C : A()
class D : A()
class BRepo : RepoImp<B>()
class CRepo : RepoImp<C>()
class DRepo : RepoImp<D>()
interface Repo<T : A> {
fun setParam(param: T)
fun getParam(): T
}
abstract class RepoImp<T : A> : Repo<T> {
private lateinit var parameter: T
override fun setParam(param: T) {
parameter = param
}
override fun getParam(): T {
return parameter
}
}
class Repo2<T : A>(val repo: Repo<in T>) {
fun process(b: DataContainer<T>) {
repo.setParam(b.b)
}
}
data class DataContainer<T : A>(
val a: String,
val b: T
)
fun main() {
val repoB = BRepo()
val repoC = CRepo()
val repoD = DRepo()
val repo2 = Repo2(repoB)
val repo3 = Repo2(repoC)
val repo4 = Repo2(repoD)
repo2.process(DataContainer("Process B type", B()))
repo3.process(DataContainer("Process C type", C()))
repo4.process(DataContainer("Process D type", D()))
println(repo2.repo.getParam())
println(repo3.repo.getParam())
println(repo4.repo.getParam())
}
I am designing a DSL and run into a requirement where I have a variable which could be assigned to different ways. Greatly simplified, I would like to set value property either by an integer or by an expression in String. (The real need is even more complex.)
I would like to write in my DSL:
value = 42
or
value = "6*7"
Behind the scene, the value will be stored in a DynamicValue<Int> structure which contains either an integer or the expression.
class DynamicValue<T>(dv : T?, expr : String) {
val directValue : T? = dv
val script : String? = expr
...
}
I tried several ways (delegate, class, etc), but none of them provided these syntax.
Is there a way to declare this union like structure?
What do you think about the following syntax:
value(42)
value("6*7")
//or
value+=42
value+="6*7"
You can do this with operator functions:
class DynamicValue<T>() {
var dv: T? = null
var expr: String? = null
operator fun invoke(dv : T) {
this.dv = dv
this.expr = null
}
operator fun invoke(expr: String) {
this.dv = null
this.expr = expr
}
operator fun plusAssign(dv : T) {
this.dv = dv
this.expr = null
}
operator fun plusAssign(expr: String) {
this.dv = null
this.expr = expr
}
}
You can't redefine the assign operator in Kotlin, therefor the pure syntax value=42 is not possible.
But I wouldn't go with operator functions, it's to magical. I would do this:
val value = DynamicValue<Int>()
value.simple=42
value.expr="6*7"
class DynamicValue2<T>() {
private var _dv: T? = null
private var _expr: String? = null
var simple: T?
get() = _dv
set(value) {
_dv = value
_expr = null
}
var expr: String?
get() = _expr
set(value) {
_expr = value
_dv = null
}
}
Rene's answer gave me the lead and finally I turned up with this solution.
In this solution I took all my requirements in (the ones I dropped out in my original question) so this became much more complicated than my original question would have required.
My whole requirement was to be able to add static values or scripts (snippets) running on a well guarded context. These script would be stored, and executed later. I wanted to enable the whole power of the IDE when writing the script, but would like to guard my scripts from code injections and help the user to use only the context values the script requires.
The trick I used to achieve this is to enable adding script in kotlin, but before I run the whole DSL script and create the business objects, I convert the script into a string. (This string will be executed later in a guarded, wrapped context by JSR233 engine.) This conversation forced me to tokenize the whole script before execution and search/replace some of the tokens. (The whole tokenizer and converter is rather long and boring, so I won't insert here.)
First approach
What my goal was to be able to write any of this:
myobject {
value = static { 42 } // A static solution
value = static { 6 * 7 } // Even this is possible
value = dynamic{ calc(x, y) } // A pure cotlin solution with IDE support
value = dynamic("""calc(x * x)""") // This is the form I convert the above script to
}
where calc, x and y are defined in the context class:
class SpecialScriptContext : ScriptContextBase() {
val hello = "Hello"
val x = 29
val y = 13
fun calc(x: Int, y: Int) = x + y
fun greet(name: String) = println("$hello $name!")
}
So let's see the solution! First I need a DynamicValue class to hold one of the values:
class DynamicValue<T, C : ScriptContextBase, D: ScriptContextDescriptor<C>>
private constructor(val directValue: T?, val script: String?) {
constructor(value: T?) : this(value, null)
constructor(script: String) : this(null, script)
}
This structure will ensure that exactly one of the options (static, script) will be set. (Don't bother with the C and D type parameters, they are for context-based script support.)
Then I made top level DSL functions to support syntax:
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> static(block: () -> T): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(value = block.invoke())
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(s: String): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(script = s)
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(block: C.() -> T): DynamicValue<T, C, D> {
throw IllegalStateException("Can't use this format")
}
An explanation to the third form. As I wrote before, I don't want to execute the block of the function. When the script is executed, this form is converted to the string form, so normally this function would never appear in the script when executed. The exception is a sanity warning, which would never be thrown.
Finally added the field to my business object builder:
#PlsDsl
class MyObjectBuilder {
var value: DynamicValue<Int, SpecialScriptContext, SpecialScriptContextDescriptor>? = null
}
Second approach
The previous solution worked but had some flaws: the expression was not associated with the variable it set, neither with the entity the value was set in. With my second approach I solved this problem and removed the need of equal sign and most of the unnecessary curly brackets.
What helped: extension functions, infix functions and sealed classes.
First, I split the two value types into separated classes defined a common ancestor:
sealed class Value<T, C : ScriptContextBase> {
abstract val scriptExecutor: ScriptExecutor
abstract val descriptor: ScriptContextDescriptor<C>
abstract val code: String
abstract fun get(context: C): T?
}
class StaticValue<T, C : ScriptContextBase>(override val code: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>,
val value: T? = null
) : Value<T, C>() {
override fun get(context: C) = value
constructor(oldValue: Value<T, C>, value: T?) : this(oldValue.code, oldValue.scriptExecutor, oldValue.descriptor, value)
}
class DynamicValue<T, C : ScriptContextBase>(override val code: String,
script: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>)
: Value<T, C>() {
constructor(oldValue: Value<T, C>, script: String) : this(oldValue.code, script, oldValue.scriptExecutor, oldValue.descriptor)
private val scriptCache = scriptExecutor.register(descriptor)
val source = script?.replace("\\\"\\\"\\\"", "\"\"\"")
private val compiledScript = scriptCache.register(generateUniqueId(code), source)
override fun get(context: C): T? = compiledScript.execute<T?>(context)
}
Note, that I made the primary constructor internal and created a kind of copy and alter constructor. Then I defined the new functions as extension of the common ancestor and marked them infix:
infix fun <T, C : ScriptContextBase> Value<T, C>.static(value: T?): Value<T, C> = StaticValue(this, value)
infix fun <T, C : ScriptContextBase> Value<T, C>.expr(script: String): Value<T, C> = DynamicValue(this, script)
infix fun <T, C : ScriptContextBase> Value<T, C>.dynamic(block: C.() -> T): Value<T, C> {
throw IllegalStateException("Can't use this format")
}
Using the secondary copy-and-alter constructor allows to inherit the context sensitive values. Finally I initialize the value inside the DSL builder:
#PlsDsl
class MyDslBuilder {
var value: Value<Int, SpecialScriptContext> = StaticValue("pl.value", scriptExecutor, SpecialScriptContextDescriptor)
var value2: Value<Int, SpecialScriptContext> = StaticValue("pl.value2", scriptExecutor, SpecialScriptContextDescriptor)
}
Everything is in place and now I can use it in my script:
myobject {
value static 42
value2 expr "6 * 7"
value2 dynamic { calc(x, y) }
}
I have the following class:
abstract class FooTable<M, D> where M : IModel, D : IDto {
///...
fun getTableData(models: ArrayList<M>): ArrayList<D> {
// ...
}
}
And I have another class using it like:
abstract class FooPage<M, F> where M : IModel, F : IFilter {
abstract val table: FooTable<M, out IDto>
Then somewhere in my code I'm trying to do:
page.table.getTableData(arrayListOf(m1, m2)).first()
And it is giving me:
Out-projected type FooTable<out IModel, out IDto> prohibits the use of public final fun getTableData(models: kotlin.collections.ArrayList<M> /* = java.util.ArrayList<M> */): kotlin.collections.ArrayList<D> /* = java.util.ArrayList<D> */ defined in com.menighin.example.models.FooTable
Here is a fiddle with the problem: https://pl.kotl.in/ryirJJH9m
The code is:
interface IModel
interface IDto
interface IFilter
class Model : IModel
class Dto : IDto
class Filter : IFilter
class FooTable<M, D> where M : IModel, D : IDto {
fun getTableData(models: List<M>): ArrayList<D> {
return ArrayList()
}
fun testPage(masterModel: IModel, thisPage: FooPage<out IModel, out IFilter>) {
thisPage.table.getTableData(arrayListOf(masterModel)) // Error here
}
fun testTable(masterModel: IModel, masterTable: FooTable<out IModel, out IDto>) {
masterTable.getTableData(arrayListOf(masterModel)) // And error here
}
}
class FooPage<M, F> where M : IModel, F : IFilter {
val table: FooTable<M, out IDto> = FooTable()
}
fun main() {
val page = FooPage<Model, Filter>()
val a = page.table.getTableData(arrayListOf())
println("Hello, world!!!")
}
Basically there is a function in my FooTable in which I need to get reference for another Table and get its data. I guess I could pass in the data already but I'm curiou why this isn't working now...
I understand from this question that if I could change abstract val table: FooTable<M, out IDto> to abstract val table: FooTable<M, Any> it would be ok... But, as far as I know, I can't because the definition of FooTable is strict about the second parameter implementing IDto.
How can I fix this?
The problem is that in your testTable function you specify that masterTable's first generic type restriction (IModel) is out. If you change the declaration to this it compiles:
fun testTable(masterModel: IModel, masterTable: FooTable<in IModel, out IDto>)
(Alternatively, specify neither in nor out.)
This is explained here (though does take a bit getting your head round). I think that your existing code (with the IModel generic type restriction of out) states that the masterTable argument produces IModels, but there's no rules about what it takes in. Therefore the error (at least in IntelliJ) is:
Type mismatch. Required List<Nothing>. Found List<IModel>.
Because the compiler doesn't know what masterTable can take in, it can't be sure that it could take in the IModel you're trying to pass into the getTableData method.