From a23b46748557a1f224ac1d82692ad444dda48cda Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 7 Jan 2022 11:07:36 -0800 Subject: [PATCH] Add turbofish discussion to generic function RFC (#300) --- rfcs/generic-functions.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/rfcs/generic-functions.md b/rfcs/generic-functions.md index 59ec2d4a..3ac1bbba 100644 --- a/rfcs/generic-functions.md +++ b/rfcs/generic-functions.md @@ -32,6 +32,12 @@ Functions may also take generic type pack arguments for varargs, for instance: function compose(... : a...) -> (a...) return ... end ``` +Generic type and type pack parameters can also be used in function types, for instance: + +```lua +local id: (a)->a = function(x) return x end +``` + This change is *not* only syntax, as explicit type parameters need to be part of the semantics of types. For example, we can define a generic identity function ```lua @@ -86,6 +92,34 @@ Currently, Luau does not have explicit type binders, so `f` and `g` have the sam We propose supporting type parameters which can be instantiated with any type (jargon: Rank-N Types) but not type functions (jargon: Higher Kinded Types) or types with constraints (jargon: F-bounded polymorphism). +## Turbofish + +Note that this RFC proposes a syntax for adding generic parameters to functions, but it does *not* propose syntax for adding generic arguments to function call site. For example, for `id` function you *can* write: + +```lua + -- generic type gets inferred as a number in all these cases +local x = id(4) +local x = id(y) :: number +local x: number = id(y) +``` + +but you can *not* write `id(y)`. + +This syntax is difficult to parse as it's ambiguous wrt grammar for comparison, and disambiguating it requires being able to parse types in expression context which makes parsing slow and complicated. It's also worth noting that today there are programs with this syntax that are grammatically correct (eg `id('4')` parses as "compare variable `id` to variable `string`, and compare the result to string '4'"). The specific example with a single argument will always fail at runtime because booleans can't be compared with relational operators, but multi-argument cases such as `print(foo(4))` can execute without errors in certain cases. + +Note that in many cases the types can be inferred, whether through function arguments (`id(4)`) or through expected return type (`id(y) :: number`). It's also often possible to cast the function object to a given type, even though that can be unwieldy (`(id :: (number)->number)(y)`). Some languages don't have a way to specify the types at call site either, Swift being a prominent example. Thus it's not a given we need this feature in Luau. + +If we ever want to implement this though, we can use a solution inspired by Rust's turbofish and require an extra token before `<`. Rust uses `::<` but that doesn't work in Luau because as part of this RFC, `id::(a)->a` is a valid, if redundant, type ascription, so we need to choose a different prefix. + +The following two variants are grammatically unambiguous in expression context in Luau, and are a better parallel for Rust's turbofish (in Rust, `::` is more similar to Luau's `:` or `.` than `::`, which in Rust is called `as`): + +```lua +foo:() -- require : before <; this is only valid in Luau in variable declaration context, so it's safe to use in expression context +foo.() -- require . before <; this is currently never valid in Luau +``` + +This RFC doesn't propose using either of these options, but notes that either one of these options is possible to specify & implement in the future if we so desire. + ## Drawbacks This is a breaking change, in that examples like the unsound program above will no longer typecheck.