LmCast :: Stay tuned in

A Few Good Ideas in Programming Languages

Recorded: Sept. 12, 2026, 5:09 p.m.

Original Summarized

prydt's site

prydt's site

home
blog
feed

A Few Good Ideas in Programming Languages

Posted: 2026-05-10

Author: Pranoy Dutta

Here are just a few programming language features I love:
1. Flow Typing
A great idea that I first encountered in Crystal, is the idea of flow typing.
Crystal is a compiled programming langauge with static type-checking with syntax very similar to the Ruby programming language. Something that keeps Crystal feeling like a dynamically typed language is how a variable can be assigned to multiple types throughout the course of its lifetime, unlike most statically typed languages I've used. Here is an example:
my_var = 5

# my_var's type here is Int32
assert(my_var.is_a?(Int32))

if some_complex_condition()
my_var = "hello!"

# my_var's type here is String
assert(my_var.is_a?(String))
end

# What is my_var's type here?
#
# It's Int32 | String
assert(my_var.is_a?(Int32 | String))

What's most interesting about this is that there is some time when my_var is just a Int32, there is a region where it is guaranteed to be a String and then there is a region where the compiler cannot actually guarantee one or the other... so its type is the union of all the possibilities. Now if you try to run a String method on my_var, it'll fail because its not a String, its a Int32 | String and the compiler will force you to add a check like if my_var.is_a?(String) which narrows the possible types to just a String.
This is a great example of using fancy type inference to make a compiled language feel dynamic without paying much of a runtime penalty.
Typescript also has flow typing and type narrowing!
2. Borrow Checking
Rust is a systems programming language which guarantees memory safety without a garbage collector.
One large class of memory safety bugs in concurrent programs is the loathsome data race: when multiple threads read and write the same memory location simultaneously without synchronization.
The borrow checker is how Rust is able to statically prevent data races at compile-time. It enforces the following rules:

Any borrow must not out-live the scope of the owner
You can EITHER have:

exactly 1 mutable reference (&mut T)
OR 1 or more immutable references (&T)

This might remind you of a readers-writer lock which is a lock which allows many readers OR a singular writer. This is because to prevent data races, we only need to synchronize reads with respect to writes. The point of concurrent synchronization is to serialize the writes to a given memory location.
I love the borrow checker because it is such an elegant solution to the problem of data races and are a zero-cost abstraction which only comes at the cost of compile-time checks and added complexity. Added complexity is a real tradeoff but if you are writing concurrent programs, this complexity is inherent to the subject matter.
3. Contract Programming
If you've programmed much, you've probably come across the humble assert statement, which is used to error / report if a given invariant isn't upheld. Every program has invariants that it needs to uphold. Things like "this date is always past this other date" or "this tree is always balanced."
The D programming language (such an underrated language) supports contract programming where it provides syntactic support for describing more complex invariants which can be at the function-level or object-level.
D has standard assert statements:
double always_positive = 100;
assert(always_positive > 0);

But D also has enforce which is used to mark a difference in semantics. Asserts are used for program invariant violations. If an assert is triggered, this should indicate a correctness bug in our program. enforce instead throws an exception due to some external issue: something like a user input out of bounds or environment issue.
enforce(length >= 7, "Must be at least 7.");

Additionally, D has syntactic support for pre- and post-conditions for functions. Here's an example taken from Programming in D:
int daysInFebruary(int year)
out (result) {
assert((result == 28) || (result == 29));

} do {
return isLeapYear(year) ? 29 : 28;
}

Here the daysInFebruary function has a post-condition which is that it should only ever return 28 or 29; everything else is definitely a logical error in the function.
And finally, D has class-level invariants which are used to guarantee that object data is is always consistent. Here is a slightly more complex example tying everything together:
class BankAccount {
private double balance;

invariant() {
balance >= 0; // object-level invariant, always checked
}

this(double initialBalance)
in (initialBalance >= 0, "Initial balance cannot be negative")
{
balance = initialBalance;
}

void deposit(double amount)
in (amount > 0, "Deposit amount must be positive")
out (; balance == balance + amount) // checked after method returns
{
balance += amount;
}

void withdraw(double amount)
in (amount > 0, "Withdrawal amount must be positive")
in (amount <= balance, "Insufficient funds")
out (; balance >= 0, "Balance must remain non-negative")
{
balance -= amount;
}

double getBalance()
out (result; result >= 0, "Balance returned must be non-negative")
{
return balance;
}
}

This invariant() block is far cleaner and easier to maintain than having some consistency check function run at the beginning and end of every class method, and it has idomatic meaning.
-- Pranoy

Pranoy Dutta explores several valuable concepts in programming languages, focusing on features that enhance safety, expressiveness, and maintainability. One significant concept is flow typing, exemplified in languages like Crystal and TypeScript. Flow typing allows a language to maintain a dynamic feel while retaining static type-checking. This is achieved by allowing a variable to potentially hold multiple types throughout its lifetime, resulting in the variable's type being the union of all possibilities, such as an integer or a string. This system enables compile-time mechanisms to perform type narrowing by requiring explicit checks, forcing the programmer to confirm the specific type before performing type-sensitive operations, which helps in making compiled languages feel dynamic without incurring significant runtime penalties.

Another discussed feature is borrow checking, central to the memory safety guarantees of Rust, a systems programming language that avoids garbage collection. The borrow checker statically prevents data races, a common source of bugs in concurrent programs arising from simultaneous, unsynchronized access to shared memory. It enforces strict rules regarding borrowing memory, ensuring that any reference cannot outlive its owner's scope and restricting access to exactly one mutable reference or one or more immutable references. This mechanism acts as an elegant, zero-cost abstraction for concurrency control, conceptually similar to a readers-writer lock, where synchronization is applied to serialize writes to memory locations to prevent data races.

The third area of focus is contract programming, which involves defining and enforcing program invariants. While simple assert statements are used to report violations of program invariants, the author distinguishes between asserts, which indicate correctness bugs within the program, and enforce statements, which signal errors caused by external issues, such as invalid user input or environmental constraints, often throwing exceptions. Furthermore, D supports specifying pre- and post-conditions for functions, allowing developers to precisely define the expected state before and after a function execution. Class-level invariants offer a cleaner method for guaranteeing object data consistency. For instance, defining an invariant in a class can ensure that object data always remains consistent, leading to more manageable and idiomatic consistency checks compared to embedding consistency validation within every method.