DEV Community

loading...

Inversion of Inversion of Control

3Shain
Updated on ・3 min read

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 the 'framework' calls you rather than you calling the '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)
Enter fullscreen mode Exit fullscreen mode

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?
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. when connected, server sends you 'LOGIN' and you should reply 'LOGIN:<auth token>' and finally when server replies 'VERIFIED' the connection is successful.
  2. 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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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>;
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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. Sister post: Promise!==coroutine

Discussion (17)

Collapse
dz4k profile image
Deniz Akşimşek

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.

Collapse
redbar0n profile image
Magne

That’s very interesting!

The context for this article was our twitter discussion here:

twitter.com/magnemg/status/1418523...

twitter.com/san3shain/status/14189...

On programming languages, you might find my article interesting, which started the discussion in the first place:

Features of a Dream Programming Language

Collapse
jwp profile image
John Peters • Edited

The first Foo method is not in control of when the fetch returns, is not blocking during the fetch, but does not progress to next step until the system performs the await. I'm thinking it is IoC because the system does indeed resume control on to original stack in same way as a event would. I never think of this pattern as IoC rather I call it the async/await pattern.

For me, IoC is using Dependency Injection, Interfaces, and even functions, the idea being the caller is able to drive the behaviors.

Collapse
3shain profile image
3Shain Author

……I don't know if you did get my idea…The "control" I'm talking about has nothing to do with thread/blocking/stack, but a mental interpretion of logic flows.

Collapse
jwp profile image
John Peters

No I understand just don't think it's a good article on IoC, too many problems.

Thread Thread
3shain profile image
3Shain Author • Edited

Yeah someday I will find out what's not so correct. I do have some similar experiences for several times, due to have different (levels of) perspectives to a problem.

Issues so far: poor English; ill-demostrated example;

Thread Thread
jwp profile image
John Peters

But the good news is this post may be improved. The topic is good for sure.

Collapse
jwp profile image
John Peters • Edited

Also allowing a Ping Pong scheme at the application layer demonstrates a misunderstanding of
the TCP/IP layer. Why? because that's what a time out exception is for.

Collapse
3shain profile image
3Shain Author

That's for demonstration purpose……to strictly speak, I will not design a protocol like this (but I did have once integrated a service like this…)

Collapse
jwp profile image
John Peters • Edited

Yes I brought this up because many times network people will tell developers to implement a Ping Pong scheme because "nothing is wrong with the hallowed network". When the real answer is always "the network is unstable, please keep working to fix it"

The most common root cause of this type issue is, a router or switch resets due to signal failures.

Thread Thread
3shain profile image
3Shain Author • Edited

Off-topic though. Ping-pong isn't necessarily used for checking network status. E.g. measure the delay, "let me know you are not dead", etc. Maybe it's my fault to use the name ping-pong but not "echo" so someone can nitpick.

Thread Thread
jwp profile image
John Peters

The socket send receive cycle is enough to determine latency. However the protocol should be ICMP for best results because the Tcp/Ip retry logic gets in the way. Ping runs at ICMP layer.

Collapse
jwp profile image
John Peters

What does this mean?
"The key part is 'framework' call you rather you call 'framework'. ".

Collapse
3shain profile image
3Shain Author

You need the context from the first link I've provided.

Collapse
jwp profile image
John Peters • Edited

No it's just poor English. It should say:
The key part is the 'framework' calls you rather than you calling the 'framework'

Thread Thread
3shain profile image
3Shain Author

I see. Thanks. I do speak poor English 😅 and I'll keep practicing.

Thread Thread
jwp profile image
John Peters • Edited

When I first read it, I seriously didn't understand it; but was eventually able to correct it mentally after thinking about it.