Search code examples
kotlinarrow-kt

How can we use context receivers and Arrow 1.2.0 parZipOrAccumulate? No required context receiver found: Cxt


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(...)

Solution

  • 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.