What is the motivation of `context parameters`? I'...
# getting-started
u
What is the motivation of
context parameters
? I'm struggling to see it
p
Basically the problem it targets is passing many arguments down. Compose is a great example of this problem. Have you faced a situation where you have too many parameters and you repeatedly have to type the same parameters ten times to make it from the root Composable to a deep one. This is basically the problem it fixes. Basically the compiler will generate all that for you.
As a side effect of this, your dependency injection improves at the point you might not need a third party framework for di.
y
It's also amazing for DSLs! One part is that it allows extending DSLs that have "member extension functions" but being able to write them as a 3rd party user
3
w
(Not speaking for JetBrains). I really believe that to prevent misuse, one must see it as a different kind of parameter. Let's take this example:
Copy code
suspend fun User.isFriendsWith(other: User, repository: UserRepository): Boolean = ...
The last parameter feels different. The receiver and
other
are a core part of what a reader needs to read to understand the behavior of the function. The repository is simply something that happens to be necessary (but not an implementation detail, those shouldn't be visible in signature at all), but it is not critical for the understanding the functions behavior. Now you can write:
Copy code
context(repository: UserRepository)
suspend fun User.isFriendsWith(other: User): Boolean = ...
I strongly believe that context parameters should not be used for parameters that are core to the behavior of the function. Which includes most parameters that are data.
👍 3
y
I completely agree! I think that's core to the idea of Capability-based design, or
coeffects
as some literature calls them. The idea being that a function declares the capabilities it needs, and then inside it may use them freely. It basically declares what a function needs from its environment to be able to do its job.
☝️ 1
h
I was also not convinced before I used them and thought, they are nice to have. Now I am forced to use them in a Kotlin compiler plugin and they are very nice to not pass unnecessary parameters over and over. This keeps your function clean to focus on the logic.
u
so the benefit is when deep inside the "context"? i.e. not just a single
Users.isFriendsWith
? Since there to me it looks like just moving the param from one place to another and callsite is uglier
c
I don't expect they will be used much by application-level developers, but library authors are going to use them everywhere.
1
u
Why do you think? Most libraries are object oriented (deps via ctor)
c
Most Java libraries, sure. Context parameters are night and day when you write DSLs.
Let's say we want to create the DSL:
Copy code
request {
    date = 12 jan 2025
}
But, you want
jan
to only exist in your DSL (let's not pollute all other Int values!). Try that today, you'll see it's not that easy.
You can't just
Copy code
fun Int.jan(year: Int)
because that would be globally available.
So instead you have to
Copy code
interface RequestDsl {
    var date: LocalDateTime

    // This *has* to be part of the interface, it can't be an extension, because you can only have a single receiver
    infix fun Int.jan(year: Int): LocalDateTime = …
}
but that's not great, because you're not respecting extension-oriented design:
jan
shouldn't really be in the interface, it's not the main functionality of the DSL. It's just a helper function.
Also, because it has to be a member of the interface, it's not possible for application developers who use your DSL to create their own helpers. This is an example at the moment in my library KtMongo, a Kotlin DSL for MongoDB. You can't add an operator I forgot, because of this exact situation. Or more specifically, you can, but it won't be
infix
.
With context parameters, all of these problems go away:
Copy code
interface RequestDsl {
    var date: LocalDateTime
}

context(dsl: RequestDsl)
infix fun Int.jan(year: Int): LocalDateTime = …
u
I see, so dsls. (Since the examples I have seen were people trying to do DI with it at app level)
y
Also, for application developers consuming some library, it allows them to extract a piece of code that uses multiple receivers in scope to a separate function. This follows the principle that you can easily extract a piece of code into a function without changing the behaviour and without too much work
c
Personnally at app-level I would use it for things like
LoggerScope
,
CoroutineScope
,
ProgressReporter
,
TracingSpan
, Arrow's
Raise
stuff like that, but not repositories or services. I prefer those to be explicit.
👍 1
As @Youssef Shoaib [MOD] said, consider context parameters to be a way to declare the "capabilities" of a function and/or a restricted scope (for DSLs)
e
@Pablichjenkov Context parameters are not a replacement for a proper dependency injection framework. Context parameters force us to pass context through every component, which clutters the code and increases coupling. We still end up threading dependencies manually, just without naming them. That is not a win. DI frameworks exist to handle this wiring cleanly and scalably — context parameters feature does not solve that problem
p
Edgar, I agree. However, that is what I keep hearing. But seen it in practicality, I prefer a DI framework over context parameters by far. But some people who like manual DI might disagree.
👍 1