Inversion of Inversion of Control (IoC)

Definition

Inversion of Control is a common phenomenon that you come across when extending frameworks. Indeed it’s often seen as a defining characteristic of a framework.

Martin Fowler

The key part is ‘framework’ call you rather you call ‘framework’.

The ‘framework’ doesn’t need to be any specific thing. An operating system, a language runtime, or an application framework could be that ‘framework’. As you register an event handler (no matter explicit .addEventHander or implicit lifecycle), you are already using IoC.

But is IoC always good? Or we should say, is IoC always what we desired ?

IMO most of IoC is not intended (DI is the one I think of so far), but the control is by nature inverted, especially in human-computer interaction programming, because this is how the computer works: it is the user controls.

If user doesn’t input then the program just keeps idling.

The example provided in the blog above:

puts 'What is your name?'
name = gets
process_name(name)
puts 'What is your quest?'
quest = gets
process_quest(quest)

Then here comes a question: why does command line enquiry seem to keep the control ? Isn’t the thread blocking and waiting for a signal as well ?

That is true and in fact the computer is in certain degree event-driven, in form of interruptions. However, conceptually it is in control because it’s an imperative process: statements executed in order. There is a clear flow of control.

And I think sometimes we need our control back. Like when there are callback hells. And luckily coroutine(async/await) is to rescue.

// in control

async function foo() {
  const x = await fetch('http://bar.com/');
  //...
}

// control inverted

function foo() {
  fetch('http://bar.com/').then(x=>{
    //...
  })
  //...
  // we get two flows of control now?
}

Maybe this example is so simple that you don’t see any difference :/.

So let’s see another example: you are going to manage a WebSocket connection and it has some rules:

  • when connected, server sends you ‘LOGIN’ and you should reply ‘LOGIN:<auth token>’ and finally when server replies ‘VERIFIED’ the connection is successful.

  • whenever server sends ‘PING’, client must reply ‘PONG’ (in 30 seconds maybe) otherwise connection will be closed.

Normally WebSocket is event-driven and we might write a version like this

const socket = new WebSocket('foo.bar');
let isVerified = false;
socket.onopen = ()=> {
  socket.onmessage = ({data}) => {
    if(data=='LOGIN') {
      socket.send('LOGIN:'+authToken);
    }
    else if(data=='VERIFIED') {
      isVerified = true;
    }
    else if(data=='PING') {
      socket.send('PONG')
    } else {
      // process the data
    }
  }
}

This code might work. But there are edge cases like what if server doesn’t reply the expected message ?

To add more state (isLogined, isSuccessful) is a solution but it’s redundant (you might check current state whenever callback executed), and not easily-extendable if there are more steps.

However if the control is inverted, the logic will become much more natural.

Imagine we have a IoCWebSocket and it provides a modified WebSocket with extra methods:

// resolve when it's open
async ready():Promise<void>;

// resolve when there is a incoming message
// reject when websocket error
async read():Promise<string>;

Then the logic becomes:

const socket = new IoCWebSocket('foo.bar');
// ...
// assume inside an `async` function body
await socket.ready();
let next = await socket.read();
if(next!='LOGIN') {
  throw Error('Unexpected reply '+next);
}
socket.send('LOGIN:'+authToken);
next = await socket.read();
if(next!='VERIFIED') {
  throw Error('Unexpected reply '+next)
}
while(true) {
  try {
    next = await socket.read();
    if(next=='PING') {
      socket.send('PONG')
    }
    else {
      // process the data
    }
  }
  catch {
    // may catch error and re-throw if it's not due to connection closed.
    break;
  }
}

Do you notice the order (which is an implicit rule) is naturally guaranteed ?

And we may have many analogous situations like drag-n-drop, step-by-step enquiry, cheat codes , (stateful) animations and interactions(hold to activate/n-times click)……

they are procedures with extra (temporary) context informations, they’re designed to have an order, they could own their own flow of control.

Bonus: Co-routines as an alternative to state machines

This post is about ‘what’.

As for ‘how’, I’m still investigating the most optimal solutions.

Promise and async/await are fine in most cases.

Deniz Akşimşek comment

I contribute to an experimental programming language called hyperscript , which has what we call async transparency and event driven control flow .

Check out the draggable window example , it seems closely related to what you are describing.

Draggable._hs

-- Usage: _="install Draggable(dragHandle: .titlebar in me)"

behavior Draggable(dragHandle)
  init
    if no dragHandle set the dragHandle to me
  end
  on pointerdown(clientX, clientY) from dragHandle
    halt the event
    trigger draggable:start -- hooks, e.g. for adding a drop shadow while dragging
    measure my x, y
    set xoff to clientX - x
    set yoff to clientY - y
    repeat until event pointerup from document
      wait for pointermove(pageX, pageY) or
               pointerup(pageX, pageY) from document
      add { left: `${pageX - xoff}`, top: `${pageY - yoff}` }
      trigger draggable:move
    end
    trigger draggable:end
end