Manuel Wrage
08/02/2020, 11:42 AMshikasd
08/02/2020, 3:16 PMshikasd
08/02/2020, 3:20 PMManuel Wrage
08/02/2020, 7:04 PMshikasd
08/02/2020, 9:38 PMManuel Wrage
08/03/2020, 8:28 AM// provide the db
@Given
fun provideDb() = Db(given<Application>())
fun main(application: Application, userId: String) {
// call the runReader function which is the entry point
// and provide runtime arguments
// to access the db and fetch the user
val user = runReader(application) {
given<Db>().getUser(userId)
}
}
Code will then get transformed to something like this:
// provide the db
@Given
fun provideDb($context: provideDbContext) = Db($context.application())
interface provideDbContext {
fun application(): Application
}
class GeneratedContext(val application: Application) : mainLambdaContext, provideDbContext {
override fun db() = provideDb(this)
override fun application() = application
}
fun main(application: Application, userId: String) {
val user = GeneratedContext(application).db().getUser(userId)
}
interface mainLambdaContext {
fun db(): Db
}
Now i wanna create a convenience function for running DI code in the application context like so:
inline fun <R> Application.runApplicationReader(block: @Reader () -> R): R {
return runReader(this) { block() }
}
And then use it like this:
fun main(application: Application, userId: String) {
// access dependencies and get user
val user = application.runApplicationReader {
given<Db>().getUser(userId)
}
}
It should create a DI component for each inlined runReader call but in case of the runApplicationReader function it creates only one component because we cannot transform the inlined call.
Hope you understand it better now.shikasd
08/03/2020, 10:18 AMgiven
accessible only when you are running it on the spot, or you can keep its contents in a separate function?
E.g. is it always:
application.runApplicationReader {
given<Db>().getUser(userId)
}
Or it can be
application.runApplicationReader(::doStuff)
fun Reader.doStuff() {
given<Db>().getUser(userId)
}
First case is actually quite easy to handle, you have the body of the function in IR and can go through it and check/modify whatever you please
In the second case, you could have it in a separate JAR, and probably here you'll need some indication of which types you could expect from this Reader
I was thinking for awhile how to create a similar system myself, but didn't really go far there 🙂Manuel Wrage
08/03/2020, 10:51 AMgiven<T>()
is an intrinsic which can only be called from @Reader
functions (or @Reader
classes) similar to suspend
or @Composable
.
@Reader
adds an synthetic $context
parameter.
the given<T>()
call translates to a getter function in the generated context interface of the calling function.
If a @Reader
function calls another @Reader
function it will add the callee context as an supertype of it's own context and passes the context down to the callee.
the runReader
call then triggers the execution
The generated context implements the context of the @Reader
lambda which get's passed.
And satisfies dependencies via the inputs and @Given
declarations which are kind of like @Provides
and @Inject
in dagger terms.
@Given
declarations are implictly marked as @Reader
so that they can also call given<T>()
and other @Reader
functions to provide values
So yes you can extract the contents of the runReader
block to it's own function
The idea is that you call runReader
at the top level and then use @Reader
functions and @Reader
classes all the way.shikasd
08/03/2020, 11:28 AM