Have been migrating a code base to the new Raise D...
# arrow
c
Have been migrating a code base to the new Raise DSL w/ context receivers - generally going well, loving the removal of
Either
return types and
.bind()
. One pattern that has been problematic has been aggregating list of errors into a custom wrapper type; while doable in a few different ways, none were really elegant and flowed well; ended up creating
mapOrAccumulateInto
providing transformations on the happy path and a transformation on the error path of
Nel<Error>
to
CustomErrorType(Nel<Error>)
. Perhaps useful as a common pattern (or perhaps I haven’t fully grokked other built-in ways of doing this yet).
Copy code
context(Raise<ConfigurationError>)
private fun someMethod(
    node: SomeType,
    type: KType,
    context: SomeContext
): List<Any?> {

    // ensure statements here
    val typeArguments = type.typeArguments

    return typeArguments
        .zip(node.elements)
        .mapOrAccumulateInto(ConversionError::TupleConversionErrors) { (type, element) ->
            context.converterFor(type).convert(element, type, context)
        }
}


public class TupleConversionErrors(private val errors: Nel<ConfigurationError>) :ConfigurationError()

context(Raise<ConfigurationError>)
@OptIn(ExperimentalTypeInference::class)
public fun <Error : ConfigurationError, AggError : ConfigurationError, A, B> Iterable<A>.mapOrAccumulateInto(
    errorTransform: (Nel<Error>) -> AggError,
    @BuilderInference transform: Raise<Error>.(A) -> B
): List<B> {
    val errors = mutableListOf<Error>()
    val results = ArrayList<B>()
    for (item in this) {
        arrow.core.raise.fold<Error, B, Unit>(
            { transform(this, item) },
            { error -> errors.add(error) },
            { results.add(it) }
        )
    }

    return errors.toNonEmptyListOrNull()?.let { raise(errorTransform(it)) } ?: results
}
y
I think you need
withError
c
had tried with that - at the call site it adds a lot of noise, not easy/efficient to transform the errors, aggregate them and create a new custom type.
y
You can do it in the very first line of your declaration, by doing
= withError(ConversionError::TupleConversionErrors {
c
Ah neat! Yes, that is cleaner - thanks for highlighting that.
Copy code
context(Raise<ConfigurationError>)
    private fun listToTuple(
        node: ResolvedNode.ListNode,
        type: KType,
        context: ConversionContext
    ): List<Any?> {
        ensureListSize(node, elementCount)
        ensureTypeArguments(type, elementCount)

        val typeArguments = type.typeArguments

        return withError(ConversionError::TupleConversionErrors) {
            mapOrAccumulate(typeArguments.zip(node.elements)) { (type, element) ->
                context.converterFor(type).convert(element, type, context)
            }
        }
    }
y
This would reduce nesting even further!
Copy code
context(Raise<ConfigurationError>)
    private fun listToTuple(
        node: ResolvedNode.ListNode,
        type: KType,
        context: ConversionContext
    ): List<Any?> = withError(ConversionError::TupleConversionErrors) {
        ensureListSize(node, elementCount)
        ensureTypeArguments(type, elementCount)

        val typeArguments = type.typeArguments

        return mapOrAccumulate(typeArguments.zip(node.elements)) { (type, element) ->
            context.converterFor(type).convert(element, type, context)
        }
    }
❤️ 2
Also, if there's certain context methods that you feel would be helpful in Arrow, It'd be great if you can mention them in arrow-context
👍 2
❤️ 1
s
Awesome project @Youssef Shoaib [MOD] 🙌 😍