💾 Archived View for yujiri.xyz › software › typing.gmi captured on 2022-06-03 at 23:21:50. Gemini links have been rewritten to link to archived content

View Raw

More Information

➡️ Next capture (2023-01-29)

-=-=-=-=-=-=-

yujiri.xyz

Software

Dynamic typing is a sin

The last few years of learning many different languages have convinced me that dynamic typing is bad for almost all use cases. Note that I'm *not* saying there are no good reasons to *use* a dynamic language, only that there are no good reasons to *make* one (there can be good reasons to use existing dynamic languages because existing static languages are imperfect; I'm after design theory here, not convincing anyone to abandon their favorite dynamic language).

I think the main benefit of static typing - catching mistakes earlier so development is faster and fewer bugs get through - is obvious, but there are less obvious ones:

Mypy

Didact

---

I think all the supposed cons of static typing are really just cons of bad type systems, and while I don't know any existing language that avoids them all, we can easily imagine a language that combines:

Interesting read: Union versus sum types

With several clear benefits of static typing, I address counterarguments.

Serialization

In a static language, you can't easily deserialize data of unknown structure, such as a JSON object.

First, almost all use cases of deserialization involve expecting a specific structure, like a Customer or the arguments to post a Comment. I have yet to run into one that doesn't. But for those that don't, powerful type systems still have ways around this, like combining maps with sum types. Consider this Rust example:

use std::io;
use serde_json::Value;

fn main() -> Result<(),io::Error> {
	let data: Value = serde_json::from_reader(io::stdin())?;
	match &data {
		Value::Null => println!("You entered null"),
		Value::Bool(b) => println!("You entered {}", if *b {"yes"} else {"no"}),
		Value::Number(n) => println!("You entered {} more than 2", n.as_f64().unwrap() - 2.),
		Value::String(s) => println!("You entered the text {}", s),
		Value::Array(a) => println!("You entered the values: {:#?}", a),
		Value::Object(o) => println!("You entered the properties: {:#?}", o),
	}
	Ok(())
}

In each branch, I have access to the data decoded as that type. I didn't have to handle them all either - I could've used a default case to fail for types I didn't want to handle.

The `o` in the `Object` branch, by the way, is of type `Map<String, Value>`, so this works recursively.

When the system is wrong

Sometimes the human knows more than the computer. No matter how smart the type checker is, static typing prevent things that a human can tell would provably work.

Good static languages have ways to assert such a requirement and panic if it isn't true. In Rust, there's `unwrap`; if I have a value of type `Option<Thing>` (which can be either `None` or `Some(Thing)` and normally requires me to handle the `None` case before I can get the `Thing` out), it has an `unwrap` method that returns the internal `Thing` and panics if it was `None`.

Something similar exists in Haskell with `fromJust`, which takes a `Maybe` type (a sum type of `Nothing` and `Just a`) and gives you the `a` unwrapped, crashing at runtime if you were wrong and it was `Nothing`.

And neither of these are language primitives; they're library code you could implement yourself.

Dynamic field access

I've sometimes taken advantage in dynamic languages of the ability to access a field of an object not by name, but by *variable* name. In Javascript, if I might want to access many different fields of `customer`, I can do `customer[field]` instead of:

switch field {
case "name": doThing(customer.name)
case "email": doThing(customer.email)
case "phone": doThing(customer.phone)
case "website": doThing(customer.website)
case "notes": doThing(customer.notes)
}

But some static languages can achieve this too; Stackoverflow users give a proof of concept:

Go example

Java example

Now, both of those are much more verbose the Javascript solution, but it doesn't have to be. It doesn't even have to be implemented via runtime reflection: although I don't know any static languages that do this, one could allow deriving an enum type of a struct's fields and casting a string to that, in such a way that it would error at runtime if the string wasn't the name of one of the fields. I can easily imagine a static language that makes this almost as concise as the Javascript solution.

contact

subscribe via RSS