编程语言中的一些好点子
A Few Good Ideas in Programming Languages

原始链接: https://prydt.xyz/blog/a-few-good-ideas-in-pl/

在这篇文章中,Pranoy Dutta 重点介绍了三项能够提升开发效率与代码安全性的编程语言特性: 1. **流式类型(Flow Typing,Crystal/TypeScript):** 该特性允许变量在生命周期内持有多种类型。编译器会追踪这些类型并使用“类型收窄”(如 `is_a?` 检查)来确保类型安全,从而在提供动态语言灵活性的同时,兼顾编译型语言的性能。 2. **借用检查器(The Borrow Checker,Rust):** 为了消除并发编程中的数据竞争,Rust 的借用检查器强制执行严格的所有权规则:同一时间,一段数据只能拥有一个可变引用或多个不可变引用。这创造了一种优雅的零成本抽象,能够在编译阶段防止内存安全错误。 3. **契约式编程(Contract Programming,D):** D 语言通过内置对不变式、前置条件和后置条件的支持,提高了代码的可靠性。通过区分内部错误(`assert`)与外部环境问题(`enforce`),并将验证逻辑直接附加到函数和类上,D 语言使得维护复杂的对象状态和程序不变式变得更加简单。 总而言之,这些特性将错误检测前置到了编译阶段,通过富有表现力的高级语法,减少了运行时的故障。

Hacker News 最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 编程语言中的一些好想法 (prydt.xyz) 15 分,由 airhangerf15 发布于 4 小时前 | 隐藏 | 过往 | 收藏 | 讨论 | 帮助 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Posted: 2026-05-10

Author: Pranoy Dutta


Here are just a few programming language features I love:

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!

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.

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

联系我们 contact @ memedata.com