展示 HN:我在我的编程语言中实现了泛型。
Show HN: I implemented generics in my programming language

原始链接: https://axe-docs.pages.dev/features/generics/

泛型允许编写在编译时适应不同类型函数,避免运行时类型检查。使用`when T is [type]`子句,单个函数体可以包含特定于类型的逻辑。例如,`some_function[T]`可以使浮点数加倍并递增整数。 更复杂的场景涉及多个类型参数,例如`list_contains[T, T2]`函数,它使用不同的比较方法搜索整数和字符串列表。安全的类型转换在`when`子句*内部*使用,确保类型正确性。 泛型也适用于返回值;`maybe_double[T]`返回类型为`T`的值,根据`T`执行浮点数或整数乘法。 重要的是,泛型函数可以调用其他泛型函数,类型会自动推断,从而实现分层抽象。该系统提供了一种表达性强、类型安全的代码,其中特化仍然是显式且可预测的,行为就像为每种类型量身定制一样。

## Axe 编程语言:泛型实现 一位开发者宣布其编程语言 Axe ([axe-docs.pages.dev](https://axe-docs.pages.dev)) 实现了泛型。核心讨论在于,该实现是否真正符合“泛型”的定义,还是更准确地说是类型特定的代码分支。 许多评论者认为,真正的泛型,例如 C++ 中的泛型,或者从概念上讲,应该允许一个函数在不进行显式类型检查的情况下,对不同类型进行统一操作——依靠编译时代码生成或鸭子类型。然而,Axe 的实现使用类型信息来选择不同的代码路径(本质上是 `instanceof` 切换),这引发了对违反“参数性”的担忧——即泛型函数应该无论使用何种特定类型,行为都应保持一致。 虽然有些人承认这不一定是*坏*事,但它偏离了泛型的预期行为,并引入了显式类型检查的代码气味。 另一些人指出潜在的好处,例如启用并行性,并讨论了该语言的潜在用例。 还有一点需要注意的是,它与 TI-84 语言和 Haxe 存在名称冲突。
相关文章

原文

Generic functions in Axe can be defined using type parameters, allowing a single function body to express behaviour that adapts to multiple concrete types. A simple example would look like this:

def some_function[T](arg: T): T {
    when T is float {
        return arg * 2.0;
    }
    when T is i32 {
        return arg + 1;
    }
    return arg;
}

Here, the function’s logic is specialized at compile time based on the concrete type bound to T. If the callsite is some_function(2);, the compiler resolves T as i32 and selects the corresponding branch, returning the value incremented by one. If the argument is a floating-point value, the float-specific branch applies and the value is doubled. For any other type, none of the when clauses match, and the function falls back to returning the argument unchanged. The important point is that the decision is driven entirely by type information, not by runtime inspection.

Generic when/is clauses can also be applied in more complex, nested contexts, where multiple type parameters interact with each other.

In the following example, the list_contains function is written to operate on both integer lists and string lists, even though the comparison logic differs between the two cases:

def list_contains[T, T2](lst: T, value: T2): bool {
    for mut i = 0; i < lst.len; i++ {
        when T2 is i32 and T is IntList {
            if lst.data[i] == cast[i32](value) {
                return true;
            }
        } 
        when T2 is string and T is StringList {
            if compare(lst.data[i], cast[string](value)) == 0 {
                return true;
            }
        }
    }
    return false;
}

As noted earlier in the standard library documentation, IntList and StringList are specialized structures that wrap arrays of integers and strings, respectively. The function leverages this knowledge by pairing constraints on both the list type and the element type within the same when clause. The cast operator is used to convert a generic value into a concrete type, but only in situations where the type relationship has already been established by the when condition. This makes the conversion safe and explicit, while preserving static guarantees. As a result, the function can remain generic without resorting to unchecked casts or repeated runtime type tests.

Generic functions in Axe are also capable of returning values whose behavior depends on the type parameter, while still preserving a single, well-defined return type. For example:

def maybe_double[T](arg: T): T {
    when T is float {
        return arg * 2.0;
    }
    when T is i32 {
        return arg * 2;
    }
    return arg;
}

In this case, maybe_double always returns a value of type T, but the computation performed varies depending on what T actually is. When instantiated with a floating-point type, floating-point arithmetic is used; when instantiated with a 32-bit integer, integer arithmetic applies instead. The return type is inferred directly from the input type, which makes the function predictable to use while still allowing type-specific behavior. This pattern is especially useful in numeric code, where the same conceptual operation should apply across multiple numeric representations.

Another important aspect of Axe’s generics is that generic functions can freely call other generic functions, with type parameters inferred automatically from the arguments passed. For example:

def add_or_concat[T](a: T, b: T): T {
    when T is i32 or T is float {
        return a + b;
    }
    when T is string {
        return a + b;
    }
    return a;
}

def process_values[T](x: T, y: T): T {
    return add_or_concat(x, y);
}

In this example, process_values is entirely agnostic about the concrete type of its parameters. It simply forwards them to add_or_concat, and the compiler determines which when clause applies based on the type supplied at the callsite. This allows generic abstractions to be layered without losing precision or control over behavior.

These features make generics in Axe expressive without being opaque: type-driven specialization remains explicit in the source code, while the resulting functions behave as if they were hand-written for each supported type.

联系我们 contact @ memedata.com