I’m struggling with the following code where I wou...
# arrow
k
I’m struggling with the following code where I would like to have transformed a result from one type to another which involves some weird context receiver behaviour. I would like
getQuestionById
to call
getNodeById
with a given lambda which can do the cast. However, the lambda that will be past in requires a context of type
Raise<NonEmptyList<StructuralError>>
and
getByNodeId
has the context
Raise<JourneyError>
Copy code
context(JourneyErrors)
  suspend fun <T : Node> getNodeById(
    id: StructuralNodeId,
    transformer: (StructuralNode) -> EitherNel<StructuralError, T>,
  ): Pair<StructuralNodeId, T> {
    val node = repository.getStructuralNodeById(id)
    
    val transformed = transformer(node).mapLeft { errors ->
      val message = errors.joinToString { error -> "${error.message}\n" }
      JourneyError.Fatal(message).also {
        logger.error("Failed to getNodeById: $message")
      }
    }.bind()

    return Pair(id, transformed)
  }

  context(JourneyErrors)
  suspend fun getQuestionById(id: StructuralNodeId): Pair<StructuralNodeId, Question> =
    getNodeById(id) { 
      // Complaining about no context for Raise<NonEmptyList<StructuralError>>
      Question(it.attributes) 
    }
I tried doing something with the transformation parameter definition to be something like
transformer: context(NonEmptyList<StructuralError>) (StructuralNode> -> EitherNel<StructuralError, T>
which just ends up with the
transformer
method invocation requiring 2 parameters (NonEmptyList<StructuralError>, node). How can I apply a context receiver on a lambda method parameter?
I ended up with this, which is OK but not quite what I was hoping for with the lambda parameter:
Copy code
context(JourneyErrors)
  suspend fun <T : Node> getNodeById(
    id: StructuralNodeId,
    transformer: (StructuralNode) -> EitherNel<StructuralError, T>,
  ): Pair<StructuralNodeId, T> =
    transformer(repository.getStructuralNodeById(id))
      .map { Pair(id, it) }
      .mapLeft { errors -> JourneyError.Fatal("$errors") }
      .bind()

  context(JourneyErrors)
  suspend fun getQuestionById(id: StructuralNodeId): Pair<StructuralNodeId, Question> = getNodeById(id) {
    either { Question(it.attributes) }
  }
y
Your issue here is using
EitherNel
. What you instead want is something using
withError
. Code to follow shortly...
Copy code
context(JourneyErrors)
  suspend fun <T : Node> getNodeById(
    id: StructuralNodeId,
    transformer: context(Raise<Nel<StructuralError>>) (StructuralNode) -> T,
  ): Pair<StructuralNodeId, T> {
    val node = repository.getStructuralNodeById(id)
    
    return withError(Nel<StructuralError>::toJourneyError) {
      id to transformer(node)
    }
  }

  fun Nel<StructuralError>.toJourneyError(): JourneyError {
    val message = errors.joinToString { error -> "${error.message}\n" }
    return JourneyError.Fatal(message).also {
      logger.error("Failed to getNodeById: $message")
    }
  }

  context(JourneyErrors)
  suspend fun getQuestionById(id: StructuralNodeId): Pair<StructuralNodeId, Question> =
    getNodeById(id) { 
      Question(it.attributes) 
    }
Unrelated, but I think returning a
Pair
here is unnecessary since you can recover the
id
from the call itself, but I'm guessing the code is more complicated