0

Given the following functions:

context(Raise<ApplicationError>)
suspend fun getUser(legalId: UserLegalId): User
context(Raise<ApplicationError>)
suspend fun getDepartment(departmentCode: DepartmentCode): Department  
context(Raise<ApplicationError>)
suspend fun save(user: User): User

I want to invoke them in parallel and accumulate their errors:

        context(Raise<Nel<ApplicationError>>)
        override suspend fun execute(param: AddUserToDepartmentInfo): Department {
            val pair: Pair<User, Department> =
                parZipOrAccumulate(
                    {e1, e2 -> e1 + e2},
                    { getUser(param.userLegalId) },
                    { getDepartment(param.departmentCode) }
                ) { a, b -> Pair(a, b) }
            saveUserDrivenPort.save(pair.first.copy(departmentId = param.departmentCode))
            return pair.second
        }

However, the getUser() and getDepartment() invocations inside parZipOrAccumulate don't compile:

No required context receiver found: Cxt { context(arrow.core.raise.Raise<xxx.ApplicationError>) private open suspend fun getUser(...)
codependent
  • 23,193
  • 31
  • 166
  • 308

1 Answers1

0

Can you try omitting the {e1, e2 -> e1 + e2} argument? That is not needed in this case because the Nel accumulator is inferred by the outer Nel<ApplicationError>.

context(Raise<ApplicationError>)
suspend fun getUser(legalId: UserLegalId): User

context(Raise<ApplicationError>)
suspend fun getDepartment(departmentCode: DepartmentCode): Department  

context(Raise<ApplicationError>)
suspend fun save(user: User): User

context(Raise<Nel<ApplicationError>>)
suspend fun execute(param: AddUserToDepartmentInfo): Department {
  val pair: Pair<User, Department> =
    parZipOrAccumulate(
      { getUser(param.userLegalId) },
      { getDepartment(param.departmentCode) }
    ) { a, b -> Pair(a, b) }
    
  // Turn `Raise<ApplicationError>` into `Raise<Nel<ApplicationError>>`
  recover({
    saveUserDrivenPort.save(pair.first.copy(departmentId = param.departmentCode))
  }) { raise(nonEmptyListOf(it)) }

  return pair.second
}

I tried this locally, and it works for me. The Raise<ApplicationError> is exposed as ScopedRaiseAccumulate<ApplicationError> in the lambda of parZipOrAccumulate.

nomisRev
  • 1,881
  • 8
  • 15
  • As you said, removing the error combinator fixed the problem form getUser() and getDepartment() but, saveUserDrivenPort.save() still gets the "No required contextReceiver found". Can you tell why? – codependent Apr 10 '23 at 15:39
  • That is very strange, this code compiles and runs fine for me. https://gist.github.com/nomisRev/729c2f9192fb7fd68b6bf5de3bd9e93a. The editor might be confused since `parZipOrAccumulate` defines it `Raise.() ->` and you expect `context(Raise)` but it should compile fine. Maybe refreshing the window, or Gradle might fix a cache issue in the IDEA analyser. – nomisRev Apr 10 '23 at 16:55
  • I think your sample is missing this bit `saveUserDrivenPort.save()` before `return pair.second`. You can see the actual code here: https://github.com/codependent/hexapp/blob/context-receivers/src/main/kotlin/com/codependent/hexapp/application/port/in/impl/DepartmentUseCasesImpl.kt#L89 – codependent Apr 10 '23 at 20:41
  • Yes, I omitted that on purpose in favour of `TODO()`. Is the code compiling for you? I see that with `saveUserDrivenPort.save()` you're not calling `bind`. You can do that by using `getOrElse { raise(nonEmptyListOf(it)) }` or `mapLeft { nonEmptyListOf(it) }.bind()`. – nomisRev Apr 11 '23 at 07:24
  • 1
    `saveUserDrivenPort.save()` signature is `context(Raise) suspend fun save(user: User): User` so I don't have an Either to `bind()`, ?? – codependent Apr 11 '23 at 09:14
  • 1
    Should I use a function like fold/recover wrapping `saveUserDrivenPort.save()`? – codependent Apr 11 '23 at 09:15
  • 1
    Sorry for the late reply, you _transform_ the error from `Raise` using `recover` similar to how you'd use `mapLeft`. `recover({ saveUserDrivenPort.save(...) }) { raise(nonEmptyListOf(it)) }`. I updated the original example. – nomisRev Apr 16 '23 at 15:23