Hi! should one chain partially injected functions?...
# arrow
j
Hi! should one chain partially injected functions? I have this simple example :
Copy code
class A
class B
class C
class Error
class SomeDependency

fun work1(dependency: SomeDependency): suspend (a: A) -> Either<Error, B> = { a ->
    // some work with a
    B().right()
}

fun work2(dependency: SomeDependency): suspend (b: B) -> Either<Error, C> = { b ->
    // some work with b
    C().right()
}

suspend fun featureX(
    injectedWork1: suspend (A) -> Either<Error, B>,
    injectedWork2: suspend (B) -> Either<Error, C>,
    a: A,
): Either<Error, C> =
    injectedWork1(a)
        .then(injectedWork2)

suspend fun <INPUT, ERROR, OUTPUT> Either<ERROR, INPUT>.then(block: suspend (INPUT) -> Either<ERROR, OUTPUT>): Either<ERROR, OUTPUT> =
    when(this) {
        is Either.Left -> Either.Left(this.value)
        is Either.Right -> block(this.value)
    }
With this code, I can have simple low-level-domain functions,
work1
and
work2
here, and combined them in higher “feature” domain level. • Should I even try to chain them? I like the style but I could also use a
either
block with
bind
from arrow. Are there any downsides to this? If I understood FP good practises correctly, the low level functions should be chained. “The output of a function is the input of the next one”. • Is it a good practise to have the low-level functions as parameters of the feature? It feels correct to me since I can then easily test it by passing fake implementations.
a
Personally, I find this not very idiomatic in Kotlin (Btw, your then is almost a flat map)
j
I tried
map
and
flatmap
but I couldn’t pass the next function as a parameter due to
injectedWork2
being a
suspend
function.
Would you rather have a either block instead?
Copy code
either {
  val b = injectedWork1(a)
  injectedWork2(b)
}
y
The idiomatic way is:
Copy code
context(dependency: SomeDependency, Raise<Error>)
suspend fun work1(a: A): B {
    // some work with a
    return B()
}

context(dependency: SomeDependency, Raise<Error>)
suspend fun work2(b: B): C {
    // some work with b
    return C()
}

inline fun featureX(
    injectedWork1: (A) -> B,
    injectedWork2: (B) -> C,
    a: A,
): C =
    injectedWork2(injectedWork1(a))
And your
featureX
is just normal function composition
j
Yes, I also tried this too a while ago. I had to use a
context
function to inject all the dependencies in featureX. Fine if it’s one or two, but I have some cases up to 5 or more. I felt it creep a little bit everywhere but I’ll give it another try. Thanks for your feedback!
y
Just remember that the
context
function will be used only near the start of your application, i.e near
main
, and hence it should be fine.
k
is context parameters still experimental?
j
Yes, and it needs a compiler flag