hello. please what would be the most elegant way t...
# announcements
k
hello. please what would be the most elegant way to solve this. I have inputString representing datetime. I have list of DateTimeFormatters of supported formats. How would I parse it? each parsing attempt throws exception. at the end I would like to either throw exception or return default value if none of the formatters succeed. thx
b
List.reduce
Or List.map and test each formatter and return null if error is thrown. Then list.filternotnull.first
k
thx for suggestions. in last example if
it(str)
throws exception wouldn't it break?
b
It assumes exception is converted to null return in it
Honestly best way would be to restructure your formatters a bit: https://pl.kotl.in/BhaykMInP
z
If you need access to the actual formatter exceptions you could use runCatching too:
Copy code
formatters.asSequence()
  .map { runCatching { it.format(inputString) } }
  .let { results ->
    results.mapNotNull { it.getOrNull() }
      .firstOrNull()
      ?: throw aggregateError(results)
  }
n
eh a lot of these approaches aren't lazy, so they'll try all the formatters even if one works
not the sequence one
that's fine, but you don't want to do list.map's
k
this is what I've ended up with
Copy code
val date: LocalDateTime = dateString.let {
    supportedDateTimeFormats.asSequence()
        .mapNotNull { formatter -> try {LocalDateTime.parse(it, formatter)} catch (e: DateTimeException) {null} }
        .firstOrNull()
        ?: throw DateTimeException("Non compatible date time format for $it")
}
as I see it looks similiar to @Zach Klippenstein (he/him) [MOD] solution :)
n
I'm confused how you're using both
it
and
formatter
(a named argument) inside that lambda
ah it's from the outer let
personally I'd probably avoid nesting scope functions like that, can chain them instead
@Zach Klippenstein (he/him) [MOD] i stared at your code for a bit and I found it really hard to convince myself that it worked. So I ran it in the IDE. If you do that, you actually run all the formatters twice on an error
not sure if you were aware of that
it's actually surprisingly difficult to write this problem "elegantly" without wasted effort. seems like just using for loops is the best?
z
Yep, that sounds right.
Is it actually necessary to optimize anything here though? How many formatters are you actually dealing with, and how often are you running this operation?
n
I dunno, it seems wasteful to run formatters twice or run formatters when previous ones have been successful
z
Sure, but there’s a tradeoff between optimizing the actual computation work you’re doing vs writing simple and clean code. If the performance impact of the non-computationally-optimal solution is insignificant, then it’s probably better to have simpler code.
n
i'm not sure if I think it's simpler/cleaner than using a for loop
Copy code
val date = run {
        val errors = mutableListOf<Throwable>()
        for (f in formatters) {
            val v = f.runCatching { return@run format(inputString) }.onFailure { errors += it }
        }
        throw aggregateError(errors)
    }
1
That seems no less readable to me, there are trade-offs both ways, an advantage here is no need to track all the nullability logic
z
yea that seems very clean. You could also write something like that with scan, but it would be uglier.
n
TIL scan 😛 Thanks.
m
your problem calls for the Chain of Responsibility pattern, which will solve it efficently and decouples the client from the processors and each processing element from the others
k
is not whole sequences chain of responsibility and each map is a processor?
m
No, read the pattern carefully: the difference is that che calling code is performing a direct external iteration as opposed to internal iteration in CoR; that is the client is explicitly performing a sequence/for on the elements while in CoR the client only has a reference to the chain head and each processor calls the next one. Also in CoR, the iteration is stopped after the first processor handles the computation.
n
I can't believe there's actually a design pattern name for something like this, which is a) remarkably specific, and b) is solved in like 4 lines of code with a for loop
seems a bit much