Sumant Tambe (17) [Avatar] Offline
What kind of monad is with_client<T>? It looks like a product type to me and that’s it. It’s generic on MessageType. A transform can be defined on it so it’s might be a Functor. But how about mbind?A given with_client<with_client<std::string>> has two tcp::sockets in them. Which one would survive when mbind flattens them?
Ivan Cukic (99) [Avatar] Offline
Great question. I'll have to make a note about that - if I'm not mistaken (will have to check when I get back home) if we choose the outer socket (which I'd say makes more sense for this), the monad rules will be satisfied.
534639 (3) [Avatar] Offline
Ivan Cukic wrote:if I'm not mistaken if we choose the outer socket, the monad rules will be satisfied.

Isn't that an immediate contradiction to monad rule, no.2? If
construct(a) := a×b
then rule no. 2 states that
mbind((a×c), construct) == a×c
, but choosing the outer socket would mean that
mbind((a×c), construct) == a×b

If we choose the inner socket instead, we instead get a contradiction to rule no.1, I think. If
g(a) := f(a)×c
, rule 1 reads
mbind((a×b), g) == f(a)×c
, but choosing the inner socket would mean that
mbind((a×b), g) == a×b

So I think that a cartesian product can't easily be made a monad... But we don't need a monad at all, do we? Can't we easily live with it being "just" a functor? After all, we want to enhance a value with a client once and then lift usual (non monad-creating) functions.
Ivan Cukic (99) [Avatar] Offline
I checked the rules, but forgot to post the explanation here.

You are right that it is not quite as simple as returning the innermost/outermost client - but it is close.

The main problem in this case is actually in something else - in the construct function itself. The construct function can not return construct(a) = a×b because it would need to get b from somewhere.

A cartesian product (a pair) is a monad when the 'other type' is a monoid. In this case, we have the main type T and the context type socket*.

The socket* is a monoid similar to Maybe/optional:
    mempty = nullptr
    mappend(x, y) := x ? x : y // leftmost non-empty value (not the only
                               // solution for mappend, but the easiest)

The construct function needs to return the value coupled with a nullptr client (mempty).
    construct(a) {
        return { a, nullptr };
    mbind({ a, client1 }, f) := {
        auto [ b, client2 ] = f(a);
        return { b, mappend(client1, client2) };

(it is quite tedious to write this in pseudo-C++17 syntax smilie )

Then the identity laws are satisfied:

    mbind(construct(a), f)
                           == mbind({a, nullptr}, f)
                           // ... f(a) returns {b, client}, mbind mappends nullptr to client 
                           == { b, client }
                           == f(a)

    mbind({ a, client }, construct) 
                           // ... construct(a) gives {a, nullptr}, mbind mappends
                           == { a, client }

As for this particular example, you are right, functor with an apply_for_client function would be sufficient.

Sumant Tambe (17) [Avatar] Offline
I'm not convinced that it's as simple as just dropping one of the sockets when flattening. The idea of with_client<T> is to send result T to a socket (a client). It's a no-op when socket is null. Agreed. But when socket is non-null, it represents a computation: "send a result of type T to a client identified by this socket".

A dependent computation passed to mbind can take the result of type T and return U to be sent to a possibly different client. To make sense to the book reader, it has to do something meaningful. There may be multiple options.

1. If dependent computation returned U to the same client (socket), mbind could simply return that result assuming instead of T now U is to be sent.
2. If dependent computation returned U to a different client (socket), mbind might have to flush data to the first client and then return with_client<U> as is.

Ivan Cukic (99) [Avatar] Offline

I don't particularly like the notion that all monads represent computations.

The with_client represents a value that can have some contextual information - and that is it. The fact that that client pointer can be used to reply to the client does not infer that it has to, nor that it is its main point. It can be ignored, it can be used as the client's id when saving the bookmark to the database, etc. - depending on the function that processes said contextual information - in this case, a non-pure sink function that replies to the client and writes to cout.

If you want to cover the use-case of a mbind-transformation function can return a message for another client, you'd have to replace the pointer/optional with another monoid.

In the system which was used as the inspiration for this example, this was not necessary.

OTOH, the options for different use-cases are:

• the outermost non-null pointer/optional when only the initiating client is important;
• the innermost non-null pointer/optional when the mbind-transformation should be able to override the client (for example if the unauthorized requests should receive no response, but should be forwarded to the "authorities" - the firewall use-case);
• a list of clients when the idea is to allow the mbind-transformation to add subscribers to the message (allowing the logger to subscribe to some messages);
• replacing the current with_client with a list of {T, client} pairs if the idea is to collect different messages to be sent to different clients (Sumant: I guess you were proposing this?);
• replacing the with_client with a list of {T, list<client>} (this would need further investigating - it could be able to cover most of the above cases at once);
• or something even more complicated like the IO monad - generating runtime IO actions based on the client input with the sink function behaving like the Haskell runtime and executing said actions (though I find it hard to think of a real-world example for this ATM)...

Anyway, I like this discussion - if you think of more alternatives, please do post them here. As the text for the book is frozen, this might be a useful topic for the readers to browse through. Or it might serve as the inspiration for the 2nd edition at some point in the future. smilie