Apologies for asking to use this channel for a cod...
# arrow
d
Apologies for asking to use this channel for a code review. But I am getting to grips with functional programming, and am not sure where else to turn. The following code is to parse a string representing a polygon in the form [[1.0, 2.0],[2.0,3.122],...]. If I were not returning an Either from the function, instead returning the polygon or throwing, I would simply map over the matchResult and throw if it is unable to parse one of the x, y doubles. I'm not sure if there is a way to map over something, but escape early with an Either.left if there is an issue handling one of the map values? Here is the code as it stands now. I believe it works; I'm just not sure if there is a more idiomatic way to achieve this using Arrow?
Copy code
internal fun polygonStringToPolygon(polygonString: String): Either<String, Polygon> {
    fun collectResults(
        results: Iterable<MatchResult>,
        acc: MutableList<Position> = mutableListOf()
    ): Either<String, List<Position>> {
        if (results.none()) {
            return Either.right(acc.toList())
        }
        val (match, rest) = results.splitAt(1)
        val x = match.single().groupValues.getOrNull(1)?.toDoubleOrNull()
        val y = match.single().groupValues.getOrNull(2)?.toDoubleOrNull()
        return if (x == null || y == null) {
            Either.left("Unable to parse polygon")
        } else {
            acc.add(Position(x, y))
            collectResults(rest, acc)
        }
    }

    val regex = "\\[([0-9]*.[0-9]*),([0-9]*.[0-9]*)]".toRegex()
    val matchResult = regex.findAll(polygonString)

    return collectResults(matchResult.asIterable())
}
j
A more idiomatic way would be to use
traverse
.
traverse
iterates over a structure similar to
map
but at each step you can return an effectful result like
Either
(depending on what applicative instance you pass to traverse). When it receives a left it'll short circuit, on a right it'll accumulate.
traverse
is a bit more general than what I explained but for sequences this should be accurate. It is also lazy enough to stop when it receives a failure (at least for sequences that is!) Here is how it could look with `traverse`: (I copied this together from your code, I have not tested or looked at it in an ide ๐Ÿ™ˆ )
Copy code
internal fun polygonStringToPolygon(str: String): Either<String, Polygon> {
    val regex = "\\[([0-9]*.[0-9]*),([0-9]*.[0-9]*)]".toRegex()
    val matchResult = regex.findAll(polygonString)
     matchResult.traverse(Either.applicative()) { match ->
        val x = match.single().groupValues.getOrNull(1)?.toDoubleOrNull()
        val y = match.single().groupValues.getOrNull(2)?.toDoubleOrNull()
        if (x == null || y == null) Either.left("Unable to parse polygon")
        else Either.right(Position(x, y))
    }
}
๐Ÿ‘Œ๐Ÿผ 2
traverse
has a very weird type
fun <F, G, A, B> Kind<G, A>.traverse(ap: Applicative<F>, f: (A) -> Kind<F, B>): Kind<F, Kind<G, B>>
but if you fill this in for sequence and either you simply get:
fun <L, A, B> Sequence<A>.eitherTraverse(f: (A) -> Either<L, B>): Either<L, Sequence<B>>
which is much clearer as to what is happening
d
@Jannis thank you so much for your help, this is exactly what I was looking for ๐Ÿ™‚ Looking through the codebase, I have actually used this before; I guess over time hopefully it all sinks in.
๐Ÿ‘Œ๐Ÿผ 1
For reference, this is what I ended up with.
Copy code
internal fun polygonStringToPolygon(str: String): Either<String, Polygon> {
    val regex = "\\[([0-9]*.[0-9]*),([0-9]*.[0-9]*)]".toRegex()
    val matchResult = regex.findAll(str)
    return matchResult.traverse(Either.applicative()) { match ->
        val x = match.groupValues.getOrNull(1)?.toDoubleOrNull()
        val y = match.groupValues.getOrNull(2)?.toDoubleOrNull()
        if (x == null || y == null) Either.left("Unable to parse polygon")
        else Either.right(Position(x, y))
    }.map { it.fix().toList() }
}
๐ŸŽ‰ 1
๐Ÿ‘ 1