kotlinarrow-kt

What's the difference of the input and output parameters of Arrow's Schedule.doWhile()?


I have seen this example in the Arrow documentation:

import arrow.resilience.Schedule
 
 suspend fun main() {
     var result = ""
     Schedule
         .doWhile<String>  { input, output -> input.length <= 5 }
         .repeat {
             result += "a"
             result
         }
     println(result)
 }

I don't understand what the purpose of output is in the doWhile lambda since it is always the same as input.


Solution

  • In my personal opinion the Arrow APIs are sometimes a little bit awkward to use. This is just one example, because in this specific case input and output will always be the same and are redundant.

    Explanation

    A Schedule has two type parameters, an Input type and an Output type. It basically consists of a function that represents a ScheduleStep that takes an Input and maps it to a Decision<Input, Output>. The Output is specific to the kind of schedule step (the decision), while the Input must match the type of the repeat lambda's return value (in your case a String).

    You can create a chain of multiple ScheduleSteps where the Input will always be the same and just the Output changes, according to the specific step.

    A Schedule that can be used to later call repeat with a String but delays each repetition by one second could look like this:

    val schedule: Schedule<String, Long> = Schedule
        .spaced<String>(1.seconds)
    

    The Input type String must be explicitly set here, but the Output is a Long, defined by the spaced function. It just counts how often it was called.

    If you want to add a condition to stop the repetition, like you did in your question with doWhile, you can just append it:

    val schedule: Schedule<String, Long> = Schedule
        .spaced<String>(1.seconds)
        .doWhile { input, output -> input.length <= 5 }
    

    In the doWhile lambda, the parameter input is of type String, but the output parameter is now of type Long, because that is the Output type of the Schedule returned by spaced. You can use output to base your predicate (here input.length <= 5) on it instead, like this if you want to stop after five iterations:

    .doWhile { input, output -> output < 5 }
    

    That is the reason why you have the two different input and output parameters in the lambda. Note: Contrary to the example of your question the two parameters are different here.

    Btw., the Output of doWhile is always the same that was passed in, in this case the same Long that spaced returned.


    Now, back to the example of your question. You use doWhile directly on the companion object of Schedule, not on an instance of Schedule (as in the examples above):

    val schedule: Schedule<String, String> = Schedule
        .doWhile<String> { input, output -> input.length <= 5 }
    

    There is no previous Schedule and no previous Output type, so what happens under the hood is that an identity ScheduleStep is created first that maps the Input to itself, hence the name. The Output is not only of the same type as the Input, the actual values will always be the same too. That is why the parameters input and output in the doWhile lambda above are identical and it doesn't matter which one to use to base your condition on.

    Remember, this is only the case when you call doWhile on the companion object of Schedule (as you did in your question), not if you call it on an instance of Schedule, as in my first examples. I find it confusing to design an API that provides redundant parameters, but maybe the rationale was that the doWhile function should have the same signature, independent of how it is actually called. I do not know, but that's how it is.