carbon-lang
carbon-lang copied to clipboard
Proposal for Syntaxes - Function return type and Generics
Firstly I want to say the presentation at CPPNorth was outstanding. Great work guys.
I had a couple of suggestions - I am not sure how open you are to changing the syntax styling.
- The arrow for runType seems very PHP. I like the fact carbon derives inspiration from Typescript and Kotlin.
Current
fn functionName() -> SomeReturnType {
return SomeReturnType();
}
Proposed
// With colon just like typescript
fn functionName() : SomeReturnType {
return SomeReturnType();
}
// or when single line.
fn functionName() = SomeReturnType();
- Square bracket for generics is not intuitive. Instead, could we use angle brackets (
<T: SomeType>
)? Square brackets are more intuitive for Arrays...
The :
return type syntax is a popular request, and has the majority of upvotes in https://github.com/carbon-language/carbon-lang/discussions/1520#discussioncomment-3200131.
Given also that two existing successful successor languages (Kotlin and TypeScript) use it, there is considerable precedent.
The argument for ->
is that function types look weird with the colon syntax: var func: (i32): i32
, and so the arrow should be used in both places for consistency with var func: (i32) -> i32
. But this is optimizing for the rarer case.
Return type annotations and variable/parameter type annotations are way more common than function types, so it would make sense to optimize for a consistent syntax for them.
@emlai - Thank you for the explanation. I upvoted the comment as well.
We've been trying to avoid angle brackets because they create very serious parsing problems. For example, a correct C++ parser has to implement substantial parts of C++'s name lookup and type checking rules, because in order to parse x<y
, it has to figure out whether x
is a template, in order to decide whether the <
is a less-than operator or an open angle bracket.
I like this, it's a lot cleaner and ':' is consistent with variable declarations
We've been trying to avoid angle brackets because they create very serious parsing problems. For example, a correct C++ parser has to implement substantial parts of C++'s name lookup and type checking rules, because in order to parse
x<y
, it has to figure out whetherx
is a template, in order to decide whether the<
is a less-than operator or an open angle bracket.
That does make sense thank you for the explanation. Lexical parsing is a bit complicated. When working with golang, that was one of the things that threw me off. I am already starting to see based on the dislikes I got... there are more developers who don't like this idea.
The question is who is going to be your target users. People who come from typescript, kotlin, java, dart. Would want to use angle brackets whereas people who come from rust or go, might want to use square brackets. I'll leave it up to the discretion of the implementor.
We've been trying to avoid angle brackets because they create very serious parsing problems. For example, a correct C++ parser has to implement substantial parts of C++'s name lookup and type checking rules, because in order to parse
x<y
, it has to figure out whetherx
is a template, in order to decide whether the<
is a less-than operator or an open angle bracket.
Existing successful successor languages, TypeScript, Kotlin, and Swift (even Rust) all use <>
for generics. Their design teams have considered the options carefully, and settled on <>
. And they all handle it just fine. So calling it a very serious parsing problem is exaggeration. Also this is not C++ so we are not bound to handle <>
as bad as C++.
Carbon should learn from existing design and implementation work of languages with the same goal.
Carbon has a goal of being easy to parse for tooling, which has meant that we have considered and rejected angle brackets <
...>
, due to experience with the difficulties parsing them in C++.
The generics syntax is :!
, meaning "passed at compile time", not square brackets [
...]
, as described in #565 . Generic parameters passed explicitly, as in Slice(T)
or Vector(T)
are in round parens (
...)
. The square brackets mark parameters whose value is deduced from the types of other parameters.
2. Square brackets are more intuitive for Arrays...
All syntaxes are ugly: round brackets are more intuitive for calls. The issue with the square brackets is that if Carbon aims metaprogming, at some point there are chances that square brackets become completly ambiguous with indexing on a compile time list (let's say), so if you select type N in list A, it's not visually clear if it's an instantiation or a list extraction.
What should be discussed then becomes is more obvious, round or square is not the problem, the problem is to have an instantiation operator, like the bang in D, before the brackets, whatever is their shape.
FWIW, I agree with what @josh11b has written here:
Carbon has a goal of being easy to parse for tooling, which has meant that we have considered and rejected angle brackets
<
...>
, due to experience with the difficulties parsing them in C++.The generics syntax is
:!
, meaning "passed at compile time", not square brackets[
...]
, as described in #565 . Generic parameters passed explicitly, as inSlice(T)
orVector(T)
are in round parens(
...)
. The square brackets mark parameters whose value is deduced from the types of other parameters.
On the issue of using ->
for function returns vs. :
or something else, I think it is good to match C++ here which uses the same core symbol for its trailing return syntax.
Marking this as an issue for leads however as it is a concrete and specific question to use two alternative syntaxes.
The
:
return type syntax is a popular request, and has the majority of upvotes in #1520 (comment). Given also that two existing successful successor languages (Kotlin and TypeScript) use it, there is considerable precedent.The argument for
->
is that function types look weird with the colon syntax:var func: (i32): i32
, and so the arrow should be used in both places for consistency withvar func: (i32) -> i32
. But this is optimizing for the rarer case. Return type annotations and variable/parameter type annotations are way more common than function types, so it would make sense to optimize for a consistent syntax for them.
Well TypeScript actually uses func(): number { ... }
for normal function definition and let func: (value: number) => number;
for function types, so that would maybe also be an option? Just split it?
So fn func(): i32 { ... }
for function definitions and I would suggest var func: (i32) -> i32;
for function types? To probably be consistent with the future lambdas list.map { value -> value.x + value.y }
if Kotlin-Style Lambdas are preferred...
On the issue of using
->
for function returns vs.:
or something else, I think it is good to match C++ here which uses the same core symbol for its trailing return syntax.
I think there is some chance that we will want the same selection of options for function returns that we have for :
-- in particular, for functions returning a generic / symbolic type we might want a ->!
analogous to a :!
binding, and for constexpr
functions we might want something analogous to a template :!
binding. I think we should wait until we're designing those facilities and then reconsider this question, and leave ->
as-is for now.
Marking this as decided with "not now" based on the discussion with leads and the explanation from @zygoloid and @josh11b above.