OwenGage.com: writing

Async, Non-blocking, static or dynamic, strong or weakly typed

2021-08-20

I have my own definitions of these words. I think they make sense. I imagine there's a large selection of personal definitions out there.

Feel free to jump to the definition you think you'll disagree with most:

[A]synchronous

Async is all about the code you write. Async code looks async. Here's an example:

fetch("/somewhere").then(resp => {
    console.log(resp.status);
}).catch(console.error);

console.log("Printed before the status...")

I don't need to tell you this code is asynchronous. You can tell from how it is written.

This code is synchronous:

fn main() {
    let value = 123;
    println!("{}", value);
}

Funnily enough, async/await in most languages makes the code look synchronous again:

console.log("Printed before the status...")

try {
    let resp = await fetch("/somewhere");
    console.log(resp.status);
} catch (e) {
    console.error(e);
}

[A]synchronous is all about the style in which you write your code. Synchronous code executes in the order you'd expect for any given codes' control flow. Asynchronous code tends to use callbacks and other 'do it later' features that means the lines of code executed is less obvious.

[Non-]blocking

You can not tell from looking at the code if something is blocking or non-blocking. Here is some Go code:

resp, err := http.Get("http://example.com/")
if err != nil {
    // handle error
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)

This code looks synchronous. There are no callbacks and no awaits. Without knowing how Go works, you cannot tell from this snippet if the HTTP request hangs the entire thread waiting for a response.

In fact, Go is typically non-blocking despite being written in a synchronous style. Other Go-routines might make progress while the call to Get and ReadAll is being completed.

Blocking and non-blocking is all about other things happening. The above Go code might let nothing else occur during its execution. Or it might let any number of things happen. Blocking code is not interrupted. Non-blocking might be.

This gets murky when you think about threads and multiple CPUs. Usually you would be talking about particular APIs being [non-]blocking, like sockets in Linux.

Dynamic or statically typed

I'm not here to argue which is better.

The difference between a static and dynamically typed language, to me, is where the type lives. Here's some Python:

a = 123
a = "123"
a = 123.0

a does not have a type. It is a variable. Python is dynamic and that means variables do not have a type. The type lives with the value itself.

Here's some more Rust

fn main() {
    let a = 123;
    let a = "123";
    let a = 123.0;
}

(Ok, maybe Rust is a bad example)

Here there are actually three variables all called a, each has it's own type. The value itself doesn't 'have' a type. It exists as part of the variable.

For dynamic languages the values have a type. For static languages the variables have a type. You can blur these lines with things like Any in Rust, or even the dyanmic keyword in C#.

Strong or weakly typed

The 'strength' of a type system is down to type conversions, and is very much not a yes/no property a language.

Python is dynamic, but surprisingly strong:

>>> "1" + 2
TypeError: can only concatenate str (not "int") to str

At least compared to JavaScript:

> "1" + 2
'12'

JavaScript does a lot of type conversions without you asking, as as such is a weakly typed language. Python does less of these conversions so is stronger.

Go is very strongly typed in places, like integer conversions:

func main() {
    a := uint8(1) + uint16(2)
}
invalid operation: uint8(1) + uint16(2) (mismatched types uint8 and uint16)

Rust behaves similarly. Whereas C++ couldn't give a monkey's:

int main() {
    char a = char{1} + int{2};
}

Afterword

These are words I've used a lot, and it seems people have different understandings of them. I don't claim to know the correct meanings, so I'm interested in what other peoples definitions would be.