This is a serie of reflections on the presentation of Runar Branson Specifically, in the 39:30 he mention side effects as one of the main causes of the lack of composability. Most of the time we need the effects arranged in a different way than the one that forces the composition. For the example that he mentions: choose coffe, pay, serve, we can sustitute the one whose effect we want to reorder (pay) by one non-effectful, (add-to-cart) so that we can make many selections and pay at the end. What we do is to keep in the cart state the items selected.
But there are other reasons why side effects can prevent composition: threading, blocking for wathever reasons: communications, semaphores, callbacks, concurrency, loops waiting for some conditions... These are considered as lost cases for composition and not even mentioned. But transient demonstrates that this is not the case, that it is possible to compose well code that include threads, blockings, callbacks, communications, console interaction etc.
For example I have a program multithreaded, and each part of it could need to interact trough HTTP request and responses. Imagine that the program control a hardware with many device controllers. Can you have the program composable so that a new device controller can be added without changing anything else? To have it done we ned that each one of these controllers send and receive JSON messages and the composition must aggregate them in a single message. codifying a menu with the different devices by defining routes does not count as composition, since it is implied the modification of code in a central element, the routes configuration. The challenge is to have the convenince of the route/menu generated automatically by the composition.
To send a set of JSON registers trough a HTTP response, one option is to simply group them in a list. When the list is finished, i can send the "}". If I use HTTP 1.0 I can close the connection. if it is HTTP 1.1 I can send a empty chunk. Sequentially, the program would wait for some response from the client
sendReceive [reg1,reg2,reg3]
And wait for one response
Unfortunately this is not composable. Also, it is not multithreaded so it can not be used to group a set of computations that produce registers. The first is because if I want to compose (monadically, applicative or alternatively) the piece of code than send such list with other piece of code that send some other options, then the program will not work. I have to modify this code and add another fourth register. That is NOT composability.
I can compose them alternatively:
sendReceive reg1 <|> sendReceive reg2 <|> sendReceive reg3
The problem is that I do not know what is the last term of these operators. Remember that they are multithreaded. So I can not codify the closing of the registers in a direct way.
(In general each operand could invoque sendReceive repeatedly in different threads. In transient
choose[1..10] >>= sendReceive
would send 1 to 10 in different threads)
To allow this, I can store in the state all the threads and his state of execution so that when all the theads are in a blocking statement, for
example, the inner receive of sendReceive
, and they have no children, then I'm sure that there is no remaining JSON fragment to send.
This condition is triggered by onWaitThreads
, when all the threads are waiting in this branch of the computation:
data InitSendSequence= InitSendSequence deriving Typeable -- to flag, in the state, that JSON content is being sent
sendReceive msg= do
ms <- getRData -- avoid more than one onWaitThread, add "{" at the beguinning of the response
case ms of
Nothing -> do
onWaitThreads $ const $ msend conn $ str "1\r\n}\r\n0\r\n\r\n"
setRState InitSendSequence
msend conn "1\r\n{\r\n"
Just InitSendSequence -> return()
sendChunked msg
r <- receive
delRState InitSendSequence
return r
So onWaitThreads is activated in the first operand and only triggers when the last thread has finished. it send also the opening "{" of the set of registers. When it is triggered it executed all the tasks of the end of the sequence in a chunked encoding.
That composition Also allows to codify arbitrary expressions that uses sendRec within the alternative composition (<|>). That Is the great thing because this allows complete modularity. In this example two different programs interact with the user. One concat strings. The other sum two numbers.
minput is like sendRec, but with a extra parameter. It only receives response when his URL is invoked:
main= keep $ initNode $ concat <|> sum
where
concat= do
h <- minput "one" "first string"
w <- minput "two" "second string"
minput "" (h++w) :: Cloud ()
sum= do
x :: Int <- minput "first" "first number"
y :: Int <- minput "second" "second bumber"
minput "" (show $ x+y) :: Cloud ()
For more details see here
The inner receive is also a piece on his own. It is invoked by the message scheduler when the URL has the proper identifier.
All of this may look contrived to someone. It is very illustrative to study all the "contrived" steps that CPU manufacturers, compiler implementor and Operating system enginers have to do to make possible a simple composition of arithmetical operations in his favorite language, or to insert file IO in the middle of a program.