From 14513d36ba827cc2a3107814df39a775d3f08787 Mon Sep 17 00:00:00 2001 From: Robert Borghese <28355157+RobertBorghese@users.noreply.github.com> Date: Wed, 24 Jun 2020 05:37:44 -0400 Subject: [PATCH 1/5] Create 0000-shorthand-nullable-syntax.md --- proposals/0000-shorthand-nullable-syntax.md | 165 ++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 proposals/0000-shorthand-nullable-syntax.md diff --git a/proposals/0000-shorthand-nullable-syntax.md b/proposals/0000-shorthand-nullable-syntax.md new file mode 100644 index 0000000..a40b015 --- /dev/null +++ b/proposals/0000-shorthand-nullable-syntax.md @@ -0,0 +1,165 @@ +# Shorthand nullable-type syntax + +* Proposal: [HXP-NNNN](NNNN-filename.md) +* Author: [Robert Borghese](https://github.com/RobertBorghese) + +## Introduction + +Allow `T?` to act as shorthand for `Null`. + +```haxe +function GetFirst(arr: Array?): Int? { + if(arr != null) { + for(i in 0...arr.length) { + var v: Int? = arr[i]; + if(v != null) return v; + } + } + return null; +} +``` + +## Motivation + +There are two main motivations behind this proposal. Firstly, type parameters are tedious to write out, and they can become overwhelming long and frustrating. While this is natural and usually necessary for accurately describing types with specific type parameters, I think an argument can be made to cut down on these by providing a standard shorthand for the most common generic-type holder: `Null`. + +An example would be the function argument's type shown in the introduction. In Haxe's current state, the argument's type needs to be written out like this: `Null>>`. While this may seem like a niche case, dealing with types like this are quite common when dealing with JSON (even more annoying when an entire anonymous structure type is written out), or null-safe Haxe code in general. For example, `Null` must be explicitly assigned to variables in many cases where type-inference cannot be relied upon (such as receiving information from lower scopes like below). + +```haxe +var Data: Null = null; +for(i in 0...MyArray.length) { + if(MyArray[i] == 23) { + Data = i; + break; + } +} +if(Data != null) { + // do something +} +``` + +Long story short, due to the addition of null-safety in Haxe, there is an increased prevalence of `Null` within Haxe code, and it would be nice to reduce code bloat and not have to write unnecessarily larger type descriptions. + +--- + +The second motivation is that this syntax mashes better with Haxe's design philosophy. From what I understand, Haxe is looking to be expressive, yet simple with how it is portrayed. It should be readable to those who have never programmed in Haxe before. While `Null` follows a more common pattern for describing a type "wrapper", and type parameters are a common pattern in nearly all statically-typed languages, the `T?` pattern does an overwhelmingly better job at describing the intent of the code in a much simpler manner. + +Unlike getters/setters or anonymous functions, the syntax for nullable types in other languages with null-safety is pretty much unanimous. Going down the [TIOBE Index](https://www.tiobe.com/tiobe-index/) list for June 2020, every language in the top 50 that has `null` and null-safety uses the `T?` syntax (ex: C#, Swift, Dart, Kotlin, etc.). Everything else either has nullability for all objects (ex: Java) or has `null` removed from the language (ex: Rust). + +In other words, `T?` is a well established syntax that correlates to nullability and null-safety. Especially once .Net 5 releases, C# 8+ becomes the new standard, and massive frameworks like [Unity](https://forum.unity.com/threads/unity-c-8-support.663757/#post-4444186) begin to support it, null-safety will become even more of a buzz-phrase and its correlating syntax will be even more recognizable and mainstream. + +Now, the point being made is not that Haxe should simply copy everything that's popular; it's that since Haxe already supports null-safety, it would make sense to provide the syntax that's not only more simple and easy to read/write, but also what's essentially become the standard (especially in languages with such similar user-bases like Kotlin and Dart). + +## Detailed design + +Internally, `T?` would be identical to `Null`. I would imagine once it is parsed, its AST would be synonymous with `Null`. + +In terms of its syntax design, it would simply be a `?` at the end of any Type: +```haxe +class MyClass { + static var list: Array?; + static var number: Int?; + + function new() { + if(list == null) { + list = []; + } + list.push(this); + + number = DoThing(number, 10); + } + + function DoThing(a: Float?, b: Float): Float? { + var result: Float?; + if(a != null) { + result = b; + } + return result; + } +} +``` + +--- + +*Whitespace* + +Similar to other languages, there can be whitespace between the type and the `?`; however, no whitespace should be the standard: +```haxe +// all valid +var a: Int? = null; +var b: Int ? = null; +var c: Int + + + ? = null; +``` + +--- + +*Type Parameter Placement* + +When used with Types with type arguments, the `?` should be after the `<...>`. +```haxe +var a: Map? = null; // valid (Null>) +var b: Map? = null; // invalid +var c: Map? = null; // valid (Null>>) +``` + +--- + +*Nullable Redundancy* + +In the case of multiple `?` on the exact same Type (`Int???`), there are multiple solutions. It could throw an error (like C#), it could give a warning and default to the behavior of a single `?` (like Kotlin), or it could actually stack the `Null` wrapper class like this: `Null>>` (like Swift). Based on the reactions of C# and Kotlin, it seems pretty safe to say there is no feasible reason to use multiple `Null` wrappers, and in most cases this is probably user error. + +With that being said, Haxe already appears to handle `Null>` the same as `Null` so perhaps it's fine that way? If leaving it that way is all that takes to make this feature to be included, that's perfectly fine, but perhaps throwing an error should be considered, especially to help fix the over-redundancy some users may implement: +```haxe +var a: Int? = null; +var a: Null = null; +var b: Int?? = null; // invalid +var c: Null? = null; // invalid +var d: Null = null; // invalid +``` + +--- + +*Potential Ternary Condition Conflict Resolution* + +The only hypothetical case there could be for operator confusion/conflict would be when a Type is used as a value (as an input for `Class`) and its `?` may be confused with the ternary conditional: `A ? B : C`. Since `A` MUST be a `Bool`, I can't imagine there being a valid scenario where there would be `Type? ? B : C`, so it's probably safe to assume that every sequential `?` after a Type correlates to it. + +If something along the lines in the code shown below is a valid conflict, then `T?` should be disabled when using a Type as a value. Instead, users should be forced to use `Null`. These are very rare occurrences, so it should not affect the overall convenience this feature would provide. +```haxe +// Is this even valid Haxe code? +var a: Class = 32 == 32 ? Int? : Float?; + +// Maybe something like this could cause issues? +// Once again, not even sure if valid. +var b: Class = Int?; +var c: Class = b == Int? ? Int? : Float?; +var d: Class = b == Int ? Int? : Float?; + +// Maybe there would be some issue with a cast? +// Casting with a Type uses parentheses though... +var e: Int = someInt == cast(someOtherInt, Int?) ? 1 : 2; +``` + +## Impact on existing code + +Since this would not replace or remove `Null` (only work as an alternative), all existing code should still function like normal. + +## Drawbacks + +Since `T?` converts to `Null`, I cannot imagine any new internal issues that may occur. All existing null-safety progress should work the same. The only drawbacks may be having to rewrite existing documentation, some users being a little confused and overly-redundant with the feature (`Null?`), or some conflict with the aforementioned ternary condition operator. + +## Alternatives + +Based on the previously described motivation, `T?` appears to be the most superior option in terms of a syntax change, but perhaps the implementation could be different? Instead of adding this to the compiler directly, macros could be expanded to allow such a feature to be implemented on the user's end? However, in my opinion, since `Null` is already a top-level feature, it shouldn't be too much of a leap to add `T?` to the language. + +## Opening possibilities + +Since this would involve creating a system to read postfix operators to Types, I suppose this could open possibilities to expand macro features. + +It could also lead to the development of a feature that allows for `T[]` to be shorthand for `Array`, which I would personally love to see, but don't expect to happen. + +## Unresolved questions + +The only unresolved part would be part 4 of "Detailed design" when choosing how to handle `Null` redundancy. Though, as mentioned, it could just be left alone and there should not be any problems. From 4aa8c6ceaf4dd36d1b22b03c0597db05c02e005c Mon Sep 17 00:00:00 2001 From: RobertBorghese <28355157+RobertBorghese@users.noreply.github.com> Date: Wed, 24 Jun 2020 05:52:56 -0400 Subject: [PATCH 2/5] Changed formatting --- proposals/0000-shorthand-nullable-syntax.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/proposals/0000-shorthand-nullable-syntax.md b/proposals/0000-shorthand-nullable-syntax.md index a40b015..2b56acf 100644 --- a/proposals/0000-shorthand-nullable-syntax.md +++ b/proposals/0000-shorthand-nullable-syntax.md @@ -42,7 +42,7 @@ Long story short, due to the addition of null-safety in Haxe, there is an increa --- -The second motivation is that this syntax mashes better with Haxe's design philosophy. From what I understand, Haxe is looking to be expressive, yet simple with how it is portrayed. It should be readable to those who have never programmed in Haxe before. While `Null` follows a more common pattern for describing a type "wrapper", and type parameters are a common pattern in nearly all statically-typed languages, the `T?` pattern does an overwhelmingly better job at describing the intent of the code in a much simpler manner. +The second motivation is that this syntax meshes better with Haxe's design philosophy. From what I understand, Haxe is looking to be expressive, yet simple with how it is portrayed. It should be readable to those who have never programmed in Haxe before. While `Null` follows a more common pattern for describing a type "wrapper", and type parameters are a common pattern in nearly all statically-typed languages, the `T?` pattern does an overwhelmingly better job at describing the intent of the code in a much simpler manner. Unlike getters/setters or anonymous functions, the syntax for nullable types in other languages with null-safety is pretty much unanimous. Going down the [TIOBE Index](https://www.tiobe.com/tiobe-index/) list for June 2020, every language in the top 50 that has `null` and null-safety uses the `T?` syntax (ex: C#, Swift, Dart, Kotlin, etc.). Everything else either has nullability for all objects (ex: Java) or has `null` removed from the language (ex: Rust). @@ -79,9 +79,7 @@ class MyClass { } ``` ---- - -*Whitespace* +### Whitespace Similar to other languages, there can be whitespace between the type and the `?`; however, no whitespace should be the standard: ```haxe @@ -94,9 +92,7 @@ var c: Int ? = null; ``` ---- - -*Type Parameter Placement* +### Type Parameter Placement When used with Types with type arguments, the `?` should be after the `<...>`. ```haxe @@ -105,9 +101,7 @@ var b: Map? = null; // invalid var c: Map? = null; // valid (Null>>) ``` ---- - -*Nullable Redundancy* +### Nullable Redundancy In the case of multiple `?` on the exact same Type (`Int???`), there are multiple solutions. It could throw an error (like C#), it could give a warning and default to the behavior of a single `?` (like Kotlin), or it could actually stack the `Null` wrapper class like this: `Null>>` (like Swift). Based on the reactions of C# and Kotlin, it seems pretty safe to say there is no feasible reason to use multiple `Null` wrappers, and in most cases this is probably user error. @@ -120,9 +114,7 @@ var c: Null? = null; // invalid var d: Null = null; // invalid ``` ---- - -*Potential Ternary Condition Conflict Resolution* +### Potential Ternary Condition Conflict Resolution The only hypothetical case there could be for operator confusion/conflict would be when a Type is used as a value (as an input for `Class`) and its `?` may be confused with the ternary conditional: `A ? B : C`. Since `A` MUST be a `Bool`, I can't imagine there being a valid scenario where there would be `Type? ? B : C`, so it's probably safe to assume that every sequential `?` after a Type correlates to it. From e582b7750ce087bf39fa15cef14c5bf927dad558 Mon Sep 17 00:00:00 2001 From: RobertBorghese <28355157+RobertBorghese@users.noreply.github.com> Date: Wed, 24 Jun 2020 06:03:44 -0400 Subject: [PATCH 3/5] Formatting --- proposals/0000-shorthand-nullable-syntax.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/proposals/0000-shorthand-nullable-syntax.md b/proposals/0000-shorthand-nullable-syntax.md index 2b56acf..81d38b1 100644 --- a/proposals/0000-shorthand-nullable-syntax.md +++ b/proposals/0000-shorthand-nullable-syntax.md @@ -79,6 +79,8 @@ class MyClass { } ``` +--- + ### Whitespace Similar to other languages, there can be whitespace between the type and the `?`; however, no whitespace should be the standard: @@ -92,6 +94,8 @@ var c: Int ? = null; ``` +--- + ### Type Parameter Placement When used with Types with type arguments, the `?` should be after the `<...>`. @@ -99,8 +103,13 @@ When used with Types with type arguments, the `?` should be after the `<...>`. var a: Map? = null; // valid (Null>) var b: Map? = null; // invalid var c: Map? = null; // valid (Null>>) + +var d: Array?> = []; // valid (Array>>) +var e: Array?>? = null; // valid (Null>>>>) ``` +--- + ### Nullable Redundancy In the case of multiple `?` on the exact same Type (`Int???`), there are multiple solutions. It could throw an error (like C#), it could give a warning and default to the behavior of a single `?` (like Kotlin), or it could actually stack the `Null` wrapper class like this: `Null>>` (like Swift). Based on the reactions of C# and Kotlin, it seems pretty safe to say there is no feasible reason to use multiple `Null` wrappers, and in most cases this is probably user error. @@ -114,6 +123,8 @@ var c: Null? = null; // invalid var d: Null = null; // invalid ``` +--- + ### Potential Ternary Condition Conflict Resolution The only hypothetical case there could be for operator confusion/conflict would be when a Type is used as a value (as an input for `Class`) and its `?` may be confused with the ternary conditional: `A ? B : C`. Since `A` MUST be a `Bool`, I can't imagine there being a valid scenario where there would be `Type? ? B : C`, so it's probably safe to assume that every sequential `?` after a Type correlates to it. From bf0415a03d152b5de60bec79a026febd414b2bc5 Mon Sep 17 00:00:00 2001 From: Robert Borghese <28355157+RobertBorghese@users.noreply.github.com> Date: Wed, 24 Jun 2020 08:43:58 -0400 Subject: [PATCH 4/5] Added "Nullable-Type Output Representation"; Cleaned up "Ternary Condition Conflict" --- proposals/0000-shorthand-nullable-syntax.md | 40 +++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/proposals/0000-shorthand-nullable-syntax.md b/proposals/0000-shorthand-nullable-syntax.md index 81d38b1..e9c484d 100644 --- a/proposals/0000-shorthand-nullable-syntax.md +++ b/proposals/0000-shorthand-nullable-syntax.md @@ -127,22 +127,34 @@ var d: Null = null; // invalid ### Potential Ternary Condition Conflict Resolution -The only hypothetical case there could be for operator confusion/conflict would be when a Type is used as a value (as an input for `Class`) and its `?` may be confused with the ternary conditional: `A ? B : C`. Since `A` MUST be a `Bool`, I can't imagine there being a valid scenario where there would be `Type? ? B : C`, so it's probably safe to assume that every sequential `?` after a Type correlates to it. +Hypothetically, there may be cases where a Type being used as an expression may conflict with the ternary conditional: `A ? B : C`. As a result, users will need to either explicitly surrond the `T?` nullable type with parentheses, or resort to using the original `Null`. -If something along the lines in the code shown below is a valid conflict, then `T?` should be disabled when using a Type as a value. Instead, users should be forced to use `Null`. These are very rare occurrences, so it should not affect the overall convenience this feature would provide. ```haxe -// Is this even valid Haxe code? -var a: Class = 32 == 32 ? Int? : Float?; - -// Maybe something like this could cause issues? -// Once again, not even sure if valid. -var b: Class = Int?; -var c: Class = b == Int? ? Int? : Float?; -var d: Class = b == Int ? Int? : Float?; - -// Maybe there would be some issue with a cast? -// Casting with a Type uses parentheses though... -var e: Int = someInt == cast(someOtherInt, Int?) ? 1 : 2; +// Made up syntax rules used to represent potential error +var a: Int = Int? == Int? ? 10 : 20; // possible syntax error +var b: Int = (Int? == Int?) ? 10 : 20; // valid - parentheses +var c: Int = (Int?) == (Int?) ? 10 : 20; // valid - parentheses +var d: Int = Null == Null ? 10 : 20; // valid - original nullable syntax +``` + +--- + +### Nullable-Type Output Representation + +Text representations of the `Null` Type should now follow the more readable `T?` syntax. + +```haxe +var a: Null = null; +$type(a); // Int? + +var b: Int? = null; +$type(b); // Int? + +var c: { ?a: Int, b: Int? } = { a: 10, b: 10 }; +$type(c); // { ?a : Int, b : Int? } + +// Note: +// The above example, c, normally prints as { ?a : Null, b : Null } ``` ## Impact on existing code From a20cebb95fb0ca5936f442ce8edb3a4574ac7db3 Mon Sep 17 00:00:00 2001 From: RobertBorghese <28355157+RobertBorghese@users.noreply.github.com> Date: Wed, 24 Jun 2020 08:46:38 -0400 Subject: [PATCH 5/5] Reworded --- proposals/0000-shorthand-nullable-syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0000-shorthand-nullable-syntax.md b/proposals/0000-shorthand-nullable-syntax.md index e9c484d..03d2f56 100644 --- a/proposals/0000-shorthand-nullable-syntax.md +++ b/proposals/0000-shorthand-nullable-syntax.md @@ -154,7 +154,7 @@ var c: { ?a: Int, b: Int? } = { a: 10, b: 10 }; $type(c); // { ?a : Int, b : Int? } // Note: -// The above example, c, normally prints as { ?a : Null, b : Null } +// The above example, c, currently prints as { ?a : Null, b : Null } ``` ## Impact on existing code