534639 (3) [Avatar] Offline
#1
The book well covers how to replace loops by functional constructs, when iterating over collections (actual or generalized). But it misses to talk about classical while loops (cases where you actually repeat a task, not for different pieces of data, but until a certain condition is met).

Consider the following code (don't ask me how I came up with that example smilie):
auto enhancedInterrogation(Person torturee) {
  Answer answer;
  while (!answer.isValid()) {
    waterboard(torturee);
    answer = askForSensitiveInformation(torturee);
  }
  report(answer);
}


Using the monadic state approach from ch.10, the inner part of the loop becomes (slightly simpified):
state |= waterboard | askForSensitiveInformation;


The book does cover what options exist to handle the Repeatition:
0. Be pragmatic, keep the loop:
State state;
while (!state.hasAnswer()) {
  state |= waterboard | askForSensitiveInformation;
}
state | report;


1. Hide the loop in a second order function
State {}
  | functional_while(
    [](auto state) {
      return state.hasAnswer();
    },
    []{
      return state | waterboard | askForSensitiveInformation;
    }
  )
  | report;


2. Use a delimited range and ranges::accumulate to feed the next iteration with the result from the last
while_range(state, [](auto state) { return state.hasAnswer(); })
  | ranges::accumulate(state,
      [](auto state, auto dummy){
          return state | waterboard | askForSensitiveInformation;
      }
    )
  | report;


3. Use recursion and some kind of monad to handle the termination condition
auto enhancedInterrogation(State state) {
    return state | waterboard | askForSensitiveInformation | if_no_answer(enhancedInterrogation);
}
State{} | enhancedInterrogation | report;


I think it would be an enhancement for the book to have a section on these possibilities, pros and cons and when and how to use which one.
Ivan Cukic (99) [Avatar] Offline
#2
Great example though not something that could be printed in a book without repercussions smilie (I'm not gonna ask what was the inspiration)

The book is currently frozen (has been for quite some time now) while Manning people are checking grammar and spelling and preparing it for printing so no new content can go in (small fixes are ok).

My personal opinion for something like this is not to force FP. In languages like Haskell where you don't really have the option not to be functional, you have to think of smart ways of doing things like these. In C++, if the problem was defined like this, I'd probably go for a loop. Or I'd redefine the problem to be more functional.

Yet another alternative would be to define a generator coroutine (yay C++20) or a stateful range generator which would generate the answers, and then do a drop_while(is_not_valid) | take(1), but this would be a bit of an overkill if you do not need a range of answers somewhere in the code.

534639 (3) [Avatar] Offline
#3
Ok, thanks!

To push it further: if the requirement was to be asynchronous (and we have no coroutines yet, else I guess that would be the method of choice), i.e.
waterboard
and
askForSensitiveInformation
were to be asynchronous calls, what would you use? Spawn a separate thread (probably not worth it) just to be able to write synchronous code and block? Use future-continuations to implement 1, 2 or 3? Use reactive streams to implement your generator alternative, just with a push-model?

PS: I think a poker game has similar semantics. Might be better suited for a book. smilie
Ivan Cukic (99) [Avatar] Offline
#4
> Spawn a separate thread

Yeah, that would be an overkill. Though it is a really common approach in many projects I've had the pleasure to see the code of.

> Use future-continuations to implement 1, 2 or 3? Use reactive streams to implement your generator
> alternative, just with a push-model?

I'd say that this is a single solution. Reactive streams are, in a sense, just generalizations of continuations - continuators that can call the continuation function multiple times.

The reactive streams in the book are in fact push streams - it might look the other way round because the continuation registration goes right-to-left. But once everything is connected (ideally at program start), the value is pushed through the pipeline as soon as it appears.