What is the most idiomatic way of flattening a `Op...
# arrow
s
What is the most idiomatic way of flattening a
Option<Pair<A, Option<B>>>
to
Option<Pair<A, B>>
using arrow? This is the code were I want to apply that
Copy code
fun playerDisconnect(player: Player) {
    activeDungeons.remove(player.uniqueId) // returns a String?
        ?.let { dungeonRepo.getDungeon(it) } // returns an Option<Dungeon>
        ?.tap {
            dungeonRepo.getLastLevel(it)  // I need to somehow zip the "Dungeon" with this Option<Level> (returned by the getLastLevel), and use both, or none of the two, if there's no last level
            // filter the result using levelSpawnsManager#isLevelComplete, passing the Level I just got as argument
            // then run these methods after
            levelSpawnsManager.cancelDungeonSpawns(dungeon)
            levelSpawnsManager.resetCompletedLevels(dungeon)
            player.runLogoffCommands(dungeon)
        }
}
I've managed to do this by converting the nullable String into an
Option<String>
, but it doesn't look very idiomatic
Copy code
fun playerDisconnect(player: Player) {
    activeDungeons.remove(player.uniqueId).toOption()
        .flatMap { dungeonRepo.getDungeon(it) }
        .flatMap { dungeonRepo.getLastLevel(it).map { level -> it to level } }
        .filterNot { (dungeon, level) -> isLevelComplete(dungeon, level) }
        .tap { (dungeon, _) ->
            levelSpawnsManager.cancelDungeonSpawns(dungeon)
            levelSpawnsManager.resetCompletedLevels(dungeon)
            player.runLogoffCommands(dungeon)
        }
}
s
I’ll answer the Option/Pair part of your question first, because it’s nice to know, but I don’t think it’s the solution to solving your problem concisely and idiomatically, so I’ll offer suggestions after:
Copy code
(F<G<A>>) -> G<F<A>>
ie, OptionPair to PairOption is called
sequence
Where types
F
and
G
are functors (define map). Option is a “traversable functor”, and Pair is an “applicative bifunctor” (which is to say you can just ignore the left side). Here we can sequence then inner PO -> OP, then flatten the outer options (OOP -> OP).
Copy code
x: Option<Pair<A, Option<B>>>
x.flatMap { it.sequencePair() }
I’m not sure if the recent refactor removing kinds and typeclasses has methods for Pair/Tuple2, but it’s easy enough to
sequence
manually using plain Kotlin:
Copy code
x.flatMap { (a, ob) -> ob.map { Pair(a, it)} }
Which is essentially what you wrote above. That said, all you really want to do is get a dungeon, check the dungeon, then do cleanup work on the dungeon. The level isn’t needed, so you can skip the pairing/unpairing portions and just use the level directly in your filter:
Copy code
getLastLevel(it).exists { isLevelComplete(dungeon, level) }
So your code would look like:
Copy code
fun playerDisconnect(player: Player) {
    activeDungeons.remove(player.uniqueId).toOption()
        .flatMap { dungeonRepo.getDungeon(it) }
        .filterNot { d -> dungeonRepo.getLastLevel(d).exists { isLevelComplete(d, it) } }
        .tap { dungeon ->
            levelSpawnsManager.cancelDungeonSpawns(dungeon)
            levelSpawnsManager.resetCompletedLevels(dungeon)
            player.runLogoffCommands(dungeon)
        }
However, Option is deprecated, so you might want to something like this:
Copy code
fun playerDisconnect(player: Player) {
    activeDungeons.remove(player.uniqueId)
        ?.let { dungeonRepo.getDungeon(it) }
        ?.takeUnless { d -> dungeonRepo.getLastLevel(d)?.let { isLevelComplete(d, it) } ?: false }
        ?.also { dungeon ->
            levelSpawnsManager.cancelDungeonSpawns(dungeon)
            levelSpawnsManager.resetCompletedLevels(dungeon)
            player.runLogoffCommands(dungeon)
        }
    }
p
Option was undeprecated 🙂
👍 1
🎉 1