|
Thanks, and good question. I deliberately didn't go into the specifics about the various typeclasses at play and their laws, and how those are tested. Part of that is just for reasons of time, but the more salient reason is that I wanted to show how those of us who do purely functional programming do it in practice. So if you watched the presentation, you saw that most of my thinking was about "OK, how do I elaborate from the most trivial transformation (do nothing at all) to the one I really want?" And that proceeds compositionally: I transform this value to that value. OK, does that value have the right type? No? What do I need to do to ensure that it does? And the transformation steps have some important properties, like their scope being entirely local. I really tried to emphasize this at the end with `attemptRepeatedly`: my description of each line of the whopping three lines is exhaustive. When I say there's no point in writing a test for it, I mean that literally. There certainly are typeclasses at play, and I briefly talk about the `Catchable` typeclass, and show the ScalaDoc for it, which documents an important law: the relationship between catching ambient exceptions and the algebra provided by `Catchable`. I rely on the other typeclasses and other laws in a similar fashion. The other thing I think is pretty important is the part where I say "Let's look at cases," because the point there is that I can reason about the code by reasoning about the shape of the data it's manipulating. So if a `step` is an `attempt` of `p` that, if successful, `kill`s the retry `schedule` or, if unsuccessful, logs the exception, I'm still dealing with a `Process` of one element (by assumption that `p` will emit one element). Then `retries` will be a `Process` of 0 to infinity elements, because we put no constraints on `schedule`. So `(step ++ retries)` will be one or more elements, and because `step` `kill`s `schedule` on success, because `retries` is derived from `schedule`, `retries` is also `kill`ed. So `(step ++ retries)` is a `Process` that will emit one or more elements, with the last element being the first successful one, or the last failed element if none of the `retries` succeeds, so we take `last`. Then we just `fold` the failure or value back into a single effect, and we're done. As I discuss in the presentation, there certainly are questions. What happens if the `schedule` is empty? What happens if the `schedule` is infinite? An attendee in Q & A asked a really good question: was I sure `retries` would wait before the first retry, or was the semantics "try, then wait?" (It really is the former, but that wasn't clear from my recorded REPL session.) Of course, this isn't very impressive for a three-line example, although I think it's pretty striking that it only takes three lines, each of which can be completely reasoned about independently, to achieve a pretty significant operational goal. The point, though, is we can build entire systems this way, and in fact `attemptRepeatedly` is part of a distributed monitoring system I worked on at Intel Media/Verizon Labs, which is written entirely in this way apart from the monitoring types themselves, which present an imperative API for familiarity's sake (and which we later came to regret). I hope this helps! |