- Proposal: HXP-0003
- Author: Dan Korostelev
- Status: implemented in 4.0.0
Provide a new, more natural syntax for declaring function types with support for argument names.
Haxe supports first-class functions from the beginning, using the following syntax for type-hinting variables containing functions:
Int -> String -> Void
This syntax is often found in functional languages, such as OCaml and Haskell, but much less in non-functional and hybrid languages.
There are several issues with this syntax:
- For people familiar with functional programming languages, it suggests that auto-currying and partial application are supported, but they aren't.
- For non-FP people, it looks unfamiliar and differs too much from the actual function definition syntax.
- It doesn't support argument names. While they aren't important for the type system, they are very useful for self-documenting code, IDE signature hints, callback auto-generation, etc. (see fancy examples with screenshots here)
What we could have instead is a function type syntax that follows the new arrow function syntax:
(id:Int, name:String) -> Void
The proposed syntax would looks like this:
// no arguments
() -> Void
// single argument
(name:String) -> Void
// multiple (also, optional) arguments
(name:String, ?age:Int) -> Void
// unnamed arguments
(Int, String) -> Bool
// mixed arguments, why not
(a:Int, ?String) -> Void
This is a rather small parser change, adding additional rules after the (
token in parse_complex_type
routine.
There are different approaches regarding representation of named arguments in the AST:
- Add a new
complex_type
variant:CTNamed
for representingname:Type
part and use that forCTFunction
argument list. This would be similar toCTOptional
. - Rework
CTFunction
arguments structure to contain a list of(name, opt, type)
tuples, having empty string for names when the value comes from parsing unnamed arguments or old function type syntax.
Both options are viable and both will require changing macro data structures (which is breaking), so this is something we should discuss.
Old syntax stays in place and works like before, examples are:
Int -> Int
(Int) -> Int
(Int) -> Int -> Int
(Int -> Int) -> Int -> Int
// etc.
Mixing old and new syntax without parenthesis results in a syntax error:
(Int, Int) -> Int -> Int // syntax error: unexpected ->
(a:Int) -> Int -> Int // same
If the desired behaviour is to have a functional return type, parenthesis should be used:
(Int, Int) -> (Int -> Int)
(a:Int) -> (Int -> Int)
Depending on how we implement AST data structures (options are listed in the Representation section), this will potentionally break macros that work with haxe.macro.Expr.ComplexType
in one way or another, other than that it should not break anything because it's a completely new syntax.
The obvious drawback is that we'll have two function type syntaxes, which is why I think we should deprecate and eventually remove the old syntax. Removing the old syntax would be a huge change, but could still be an option for a major release, especially if we provide a migration tool.
I already tried the another approach of augmenting the current funtion type syntax with argument names: a:Int -> b:String -> Void
, but unfortunately that introduced a syntax ambiguity with macro reification and case patterns. One workaround for that would be to require parenthesis for named arguments, so it would be (name:Type) -> Ret
, but that suggests that one could do (name:Type, name:Type) -> Ret
while it's an invalid syntax (one would have to do (name:Type) -> (name:Type) -> Ret
instead).
ComplexType
representation for named arguments- Removal of old function type syntax