Skip to content

Instantly share code, notes, and snippets.

@agocorona
Last active November 15, 2022 08:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agocorona/4dd3a35bbf6c16213b4f9cf1743702c0 to your computer and use it in GitHub Desktop.
Save agocorona/4dd3a35bbf6c16213b4f9cf1743702c0 to your computer and use it in GitHub Desktop.
Composing the uncomposable

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment