when using `context parameters`, do all intermedia...
# getting-started
f
when using
context parameters
, do all intermediate functions need to declare them, even if only the last function in the stack accessed them? I have a sample in thread where that seems to be the case as it won't compile otherwise, but I'd like to confirm
Copy code
// won't compile if commening out this first line
context(sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope)
@Composable
fun DetailsBox(
    color: Int,
    modifier: Modifier = Modifier
) {
    Box(
        modifier = modifier,
    ) {
        InnerBox(
            color = color,
            modifier = Modifier.fillMaxWidth(.75f)
        )
    }
}

context(sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope)
@Composable
fun InnerBox(
    color: Int,
    modifier: Modifier = Modifier
) {
    with(sharedTransitionScope) {
        Box(
            modifier = modifier
                .sharedElement(
                    sharedContentState = rememberSharedContentState(key = "color-${color}"),
                    animatedVisibilityScope = animatedVisibilityScope,
                )
                .fillMaxWidth(.75f)
                .aspectRatio(1f)
                .background(color = Color(color)),
        )
    }
}
y
Yep, that's exactly how it works! It's intentionally so so that what contexts are used is explicit.
f
cool, thanks for confirming
j
But you don't have to name the context parameters in intermediate functions. You can name them
_
thank you color 2
e
The main reason to include
context
in the language is to improve how the code looks? Letting pass parameters without listing them in the function parameters list - just another mean of doing the same thing? Letting
context
parameters implicitly pass down the call stack when calling another function? Just yet another stylistic thing? I suppose it is for much more, but as I used this feature, I do not see more. Somewhere I read
context
parameters are about capability-oriented design — letting you say what this function needs to run in a type-safe, composable, and declarative way, without bloating your parameter list or leaking implementation details.
But this is achievable with function parameters
y
It makes it way, way easier than having to directly specify every single parameter where it's used. Read up on capability oriented design for more info. It's definitely a net positive, and is a nice, type safe, and static way to do dependency injection
Oh also, it works really nicely for DSLs. Previously, you could only have an extension receiver, and a dispatch receiver, and that's it. Now I can do something like:
Copy code
interface Plus<T> {
  operator fun T.plus(other: T)
}
// Bridge function
context(p: Plus<T>)
operator fun <T> T.plus(other: T) = with(p) { plus(other) }
interface HasZero<T> {
  val zero: T
}
interface Inversible<T> {
  operator fun T.unaryMinus()
}
context(hz: HasZero<T>)
val zero: T get() = hz.zero
context(_: Plus<T>, _: HasZero<T>, _: Inversible<T>)
operator fun <T> T.times(count: Int) = if (count < 0) -(this * (-count)) else {
  val res = zero
  repeat(count) { res += this }
  return res
}
Which is amazing!
m
Arrow's presentation at last year's Kotlin Conf really highlighted a good use for context parameters for DSLs.