Skip to content

RFC: Generic call syntax, or: Wrath of the Turbofish #1

Closed
@romdotdog

Description

@romdotdog

Summary

Jake cannot support TypeScript's generic call syntax (id<u32>(1)) due to how ambiguous it is. It is unresolvable by a conventional LR(1) parser. I have come up with three solutions, and this RFC is for either voicing approval for one of them or suggesting other solutions.

Solution 1: the Turbofish

The turbofish id::<u32>(1) is present in Rust's syntax and generally considered an eyesore. Rust usually handwaves this away as syntax that doesn't occur very frequently or something that just has to be lived with. Furthermore, the :: doesn't really belong in Jake's syntax, seeing as though it appears nowhere else. Replacing it with, for example, id.<u32>(1) may be awkward. A pro of the turbofish is that it follows the precedent of a major language and it can be easily transferred from knowledge of Rust.

Example

function id<T>(x: T): T {
    return x;
}

id::<u32>(1);

Solution 2: Dependent Types

Generics are in fact a special case of dependent types (the dependency being a kind, represented by *). Thus, this solution might make the most theoretical sense, but feel completely foreign to a new user. In a nutshell, types are supplied as regular parameters to functions, and other parameters use that parameter's name to refer to the type. Dependent types will be implemented, but it's not estimated to be any time soon, and generics may as a result be delayed by the same amount of time.

Examples

function id(T: *, x: T): T {
    return x;
}

id(u32, 1);

Cons with this method include confusion if types look like terms. For example, if Jake had values-as-types:

id(4, 4); // identity takes two parameters?
// in reality, the first `4` is a type, while the second is the actual value

Solution 3: Type Ascription

This solution may be the simplest and easiest to understand, however, it may conflict with other parts of the language. This conflict is described after the following example. This feature has actually been accepted into Rust for over half a decade, never being stabilized.

Examples

function id<T>(x: T): T {
    return x;
}

id(1): u32; // 1 is coerced to be u32 to fit T
// or
id(1: u32); // T is inferred to be u32 to fit 1

Conflicts

Jake was initially intended to use canonical pattern matching in much the same way as Rust, but to be more familiar to TypeScript users and less noisy in general (i.e. no need for if let), the current Jake spec uses the same syntax for "flow-sensitive" pattern matching. Should this solution be accepted, this conflict would have to be resolved in some way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions