This issue lists and compares several syntax proposals that have been proposed and initially discussed in issue #55. It should help to come to a decision which syntax proposal should be used.
Note: The following aspects have not been discussed yet and are not part of this overview/comparison:
- variance (covariant, contravariant and invariant)
- type restrictions
- Examples
- Syntax proposals
- Comparison of syntax proposals
It might be easier for you to reason about syntax if you see some examples. Therefore, I will describe three function types Transform, Filter and Map and an interface type Collection in different syntaxes. I provide different writing styles of each syntax. For example, you might write Filter or Map with use of Transform or using an inline type expression instead.
In this section I describe the signature of the three generic function types. Further, I describe each type argument and give an example of an function implementation in JavaScript with its type as a comment.
Name: Transform
Type arguments: Input, Output
Signature: Transform(Input): Output
Description: A function that takes a value of type Input and returns a value of type Output.
Example: The function len takes a String as parameter and returns its length as type Number. Thus it is of type Transform where Input is String and Output is Number.
// Transform(String): Number
function len(str) {
return str.length;
}Name: Filter
Type arguments: Element
Signature: Filter(Element => Boolean, Element[]): Element[]
Description: Filter elements of type Element in an array. Take a function of type Element => Boolean (is equivalent to Transfrom(Element): Boolean) as first parameter. Takes array of type Element[] as second parameter. Returns an array of type Element[].
Example: An generic implementation might look like this:
// Filter(Element => Boolean, Element[]): Element[]
function filter(predicate, elements) {
return elements.filter(predicate);
}The generic type argument Element has to be inferred from the passed arguments of the filter call.
// Transform(Number): Boolean
function isEven(x) {
return x % 2 === 0;
}
// Transform(String): Boolean
function isShort(str) {
return str.length < 4;
}
// Number[]
var numbers = [1, 2, 3, 4];
// String[]
var strings = ["rtype", "is", "cool"];
// Filter(Number => Boolean, Number[]): Number[]
filter(isEven, numbers); // [2, 4]: Number[]
// Filter(String => Boolean, String[]): String[]
filter(isShort, strings); // ["is"]: String[]
// type conflicts if incompatible types are use as type argument `Element`
filter(isEven, strings); // Error: In "Filter" the first type argument "Element" couldn't match both (incompatible) types Number and String
filter(isShort, numbers); // Error: In "Filter" the first type argument "Element" couldn't match both (incompatible) types String and NumberName: Map
Type arguments: Input, Output
Signature: Map(Input => Output, Input[]): Output[]
Description: Filter elements of type Element in an array. Take a function of type Element => Boolean (is equivalent to Transfrom(Element): Boolean) as first parameter. Takes array of type Element[] as second parameter. Returns an array of type Element[].
Example: Map elements of type Input to type Output. Take a function of type Input => Output (is equivalent to Transfrom(Input): Output) as first parameter. Takes array of type Input[] as second parameter. Returns an array of type Output[].
// Map(Input => Output, Input[]): Output[]
function map(transform, elements) {
return elements.map(transform);
}The generic type arguments Input and Output have to be inferred from the passed arguments of the map call.
// Transform(Number): Number
function square(x) {
return x * x;
}
// Transform(String): Number
function len(str) {
return str.length;
}
// Number[]
var numbers = [1, 2, 3, 4];
// String[]
var strings = ["rtype", "is", "cool"];
// Map(Number => Number, Number[]): Number[]
map(square, numbers); // [1, 4, 9, 16]: Number[]
// Map(String => Number, String[]): Number[]
map(len, strings); // [5, 2, 4]: Number[]
map(square, strings); // Error: In "Map" the first type argument "Input" couldn't match both (incompatible) types Number and String
map(len, numbers); // Error: In "Map" the first type argument "Input" couldn't match both (incompatible) types String and NumberTo keep it simple, I describe an interface Collection that is satisfied by most common data structure objects (for example Array).
Name: Collection
Type arguments: Element
Signature:
// takes a type argument Element
interface Collection {
filter(Element => Boolean): Collection // with elements of type `Element`
// map is generic and takes an additional type argument `NewElement`
map(Element => NewElement): Collection // with elements of type `NewElement`
}
Description: The interface Collection takes one type argument Element that is visible in the interface body. All elements in the data structure are of this type Element. A Collection has methods filter and map. They do the same as the functions filter and map I described in the chapter above, but since they are methods, the data structure is (implicitly) passed as this argument. Hence, the last argument of the corresponding function is dropped in the method.
Example: Let's see some JavaScript code with Array as Collection to see the difference between the function filter and the method filter
// Number => Boolean
function isEven(x) {
return x % 2 === 0;
}
// Number[]
// Satisfies Collection where type argument Element is Number
var numbers = [1, 2, 3, 4];
// Filter(Number => Boolean, Number[]): Number[]
filter(isEven, numbers); // [2, 4]: Number[]
// numbers: Collection where type argument Element is Number
// numbers.filter: (Number => Boolean): Number[]
numbers.filter(isEven); // [2, 4]: Number[] which satisfies Collection where type argument Element is Numberand the difference between the function map and the method map:
// Transform(String): Number
function len(str) {
return str.length;
}
// String[]
// Satisfies Collection where type argument Element is String
var strings = ["rtype", "is", "cool"];
// Map(String => Number, String[]): Number[]
map(len, strings); // [5, 2, 4]: Number[]
// numbers: Collection where type argument Element is String
// numbers.map: (String => Number): Number[]
strings.map(len); // [5, 2, 4]: Number[] which satisfies Collection where type argument Element is NumberListed in the subchapters below are type declarations of the example types Transform, Filter, Map and Collection written in different variants of each syntax proposal. In all syntax proposals the name of the type is followed by its type arguments followed by its type definition. The difference is how type arguments are written. Are they enclosed in angle or square brackets, parentheses or are they only separated by whitespace. The different variants of a syntax vary in several aspects that might influence readability:
- with or without colons after type signature
- multi- vs. single-line
- (some) type expressions enclosed in parentheses:
Input => Outputvs.(Input) => Outputvs.(Input => Output)used in a specific context - additional type argument declaration before or after the method name (here
map)
Type arguments are enclosed in angle brackets and are separated by commas. This syntax is influenced by C++, Java, C# and TypeScript.
Transform<Input, Output> Input => Output
Transform<Input, Output> (Input) => Output
Transform<Input, Output> (Input => Output)
Filter<Element> (Element => Boolean, Element[]) => Element[]
Filter<Element> (Transform<Element, Boolean>, Element[]) => Element[]
Map<Input, Output> (Input => Output, Input[]) => Output[]
Map<Input, Output> (Transform<Input, Output>, Input[]) => Output[]
interface Collection<Element> {
filter(Element => Boolean) => Collection<Element>
filter(Transform<Element, Boolean>) => Collection<Element>
<NewElement> map(transform: Element => NewElement) => Collection<NewElement>
<NewElement> map(transform: Transform<Element, NewElement>) => Collection<NewElement>
<NewElement> map(Transform<Element, NewElement>) => Collection<NewElement>
}
interface Collection<Element> {
filter(Element => Boolean) => Collection<Element>
filter(Transform<Element, Boolean>) => Collection<Element>
map<NewElement>(transform: Element => NewElement) => Collection<NewElement>
map<NewElement>(transform: Transform<Element, NewElement>) => Collection<NewElement>
map<NewElement>(Transform<Element, NewElement>) => Collection<NewElement>
}
Transform<Input, Output>: Input => Output
Transform<Input, Output>: (Input) => Output
Transform<Input, Output>: (Input => Output)
Filter<Element>: (Element => Boolean, Element[]) => Element[]
Filter<Element>: (Transform<Element, Boolean>, Element[]) => Element[]
Map<Input, Output>: (Input => Output, Input[]) => Output[]
Map<Input, Output>: (Transform<Input, Output>, Input[]) => Output[]
interface Collection<Element> {
filter: (Element => Boolean) => Collection<Element>
filter: (Transform<Element, Boolean>) => Collection<Element>
<NewElement> map: (transform: Element => NewElement) => Collection<NewElement>
<NewElement> map: (transform: Transform<Element, NewElement>) => Collection<NewElement>
<NewElement> map: (Transform<Element, NewElement>) => Collection<NewElement>
}
interface Collection<Element> {
filter: (Element => Boolean) => Collection<Element>
filter: (Transform<Element, Boolean>) => Collection<Element>
map<NewElement>: (transform: Element => NewElement) => Collection<NewElement>
map<NewElement>: (transform: Transform<Element, NewElement>) => Collection<NewElement>
map<NewElement>: (Transform<Element, NewElement>) => Collection<NewElement>
}
Transform<Input, Output>:
Input => Output
Transform<Input, Output>:
(Input) => Output
Transform<Input, Output>:
(Input => Output)
Filter<Element>:
(Element => Boolean, Element[]) => Element[]
Filter<Element>:
(Transform<Element, Boolean>, Element[]) => Element[]
Map<Input, Output>:
(Input => Output, Input[]) => Output[]
Map<Input, Output>:
(Transform<Input, Output>, Input[]) => Output[]
interface Collection<Element> {
filter:
(Element => Boolean) => Collection<Element>
filter:
(Transform<Element, Boolean>) => Collection<Element>
<NewElement> map:
(transform: Element => NewElement) => Collection<NewElement>
<NewElement> map:
(transform: Transform<Element, NewElement>) => Collection<NewElement>
<NewElement> map:
(Transform<Element, NewElement>) => Collection<NewElement>
}
interface Collection<Element> {
filter:
(Element => Boolean) => Collection<Element>
filter:
(Transform<Element, Boolean>) => Collection<Element>
map<NewElement>:
(transform: Element => NewElement) => Collection<NewElement>
map<NewElement>:
(transform: Transform<Element, NewElement>) => Collection<NewElement>
map<NewElement>:
(Transform<Element, NewElement>) => Collection<NewElement>
}
Transform<Input, Output> {
Input => Output
}
Transform<Input, Output> {
(Input) => Output
}
Transform<Input, Output> {
(Input => Output)
}
Filter<Element> {
(Element => Boolean, Element[]) => Element[]
}
Filter<Element> {
(Transform<Element, Boolean>, Element[]) => Element[]
}
Map<Input, Output> {
(Input => Output, Input[]) => Output[]
}
Map<Input, Output> {
(Transform<Input, Output>, Input[]) => Output[]
}
interface Collection<Element> {
filter {
(Element => Boolean) => Collection<Element>
}
filter {
(Transform<Element, Boolean>) => Collection<Element>
}
<NewElement> map {
(transform: Element => NewElement) => Collection<NewElement>
}
<NewElement> map {
(transform: Transform<Element, NewElement>) => Collection<NewElement>
}
<NewElement> map {
(Transform<Element, NewElement>) => Collection<NewElement>
}
}
interface Collection<Element> {
filter {
(Element => Boolean) => Collection<Element>
}
filter {
(Transform<Element, Boolean>) => Collection<Element>
}
map<NewElement> {
(transform: Element => NewElement) => Collection<NewElement>
}
map<NewElement> {
(transform: Transform<Element, NewElement>) => Collection<NewElement>
}
map<NewElement> {
(Transform<Element, NewElement>) => Collection<NewElement>
}
}
Type arguments are enclosed in square brackets and are separated by commas. This syntax is influenced by Scala.
Transform[Input, Output] Input => Output
Transform[Input, Output] (Input) => Output
Transform[Input, Output] (Input => Output)
Filter[Element] (Element => Boolean, Element[]) => Element[]
Filter[Element] (Transform[Element, Boolean], Element[]) => Element[]
Map[Input, Output] (Input => Output, Input[]) => Output[]
Map[Input, Output] (Transform[Input, Output], Input[]) => Output[]
interface Collection[Element] {
filter(Element => Boolean) => Collection[Element]
filter(Transform[Element, Boolean]) => Collection[Element]
[NewElement] map(transform: Element => NewElement) => Collection[NewElement]
[NewElement] map(transform: Transform[Element, NewElement]) => Collection[NewElement]
[NewElement] map(Transform[Element, NewElement]) => Collection[NewElement]
}
interface Collection[Element] {
filter(Element => Boolean) => Collection[Element]
filter(Transform[Element, Boolean]) => Collection[Element]
map[NewElement](transform: Element => NewElement) => Collection[NewElement]
map[NewElement](transform: Transform[Element, NewElement]) => Collection[NewElement]
map[NewElement](Transform[Element, NewElement]) => Collection[NewElement]
}
Transform[Input, Output]: Input => Output
Transform[Input, Output]: (Input) => Output
Transform[Input, Output]: (Input => Output)
Filter[Element]: (Element => Boolean, Element[]) => Element[]
Filter[Element]: (Transform[Element, Boolean], Element[]) => Element[]
Map[Input, Output]: (Input => Output, Input[]) => Output[]
Map[Input, Output]: (Transform[Input, Output], Input[]) => Output[]
interface Collection[Element] {
filter: (Element => Boolean) => Collection[Element]
filter: (Transform[Element, Boolean]) => Collection[Element]
[NewElement] map: (transform: Element => NewElement) => Collection[NewElement]
[NewElement] map: (transform: Transform[Element, NewElement]) => Collection[NewElement]
[NewElement] map: (Transform[Element, NewElement]) => Collection[NewElement]
}
interface Collection[Element] {
filter: (Element => Boolean) => Collection[Element]
filter: (Transform[Element, Boolean]) => Collection[Element]
map[NewElement]: (transform: Element => NewElement) => Collection[NewElement]
map[NewElement]: (transform: Transform[Element, NewElement]) => Collection[NewElement]
map[NewElement]: (Transform[Element, NewElement]) => Collection[NewElement]
}
Transform[Input, Output]:
Input => Output
Transform[Input, Output]:
(Input) => Output
Transform[Input, Output]:
(Input => Output)
Filter[Element]:
(Element => Boolean, Element[]) => Element[]
Filter[Element]:
(Transform[Element, Boolean], Element[]) => Element[]
Map[Input, Output]:
(Input => Output, Input[]) => Output[]
Map[Input, Output]:
(Transform[Input, Output], Input[]) => Output[]
interface Collection[Element] {
filter:
(Element => Boolean) => Collection[Element]
filter:
(Transform[Element, Boolean]) => Collection[Element]
[NewElement] map:
(transform: Element => NewElement) => Collection[NewElement]
[NewElement] map:
(transform: Transform[Element, NewElement]) => Collection[NewElement]
[NewElement] map:
(Transform[Element, NewElement]) => Collection[NewElement]
}
interface Collection[Element] {
filter:
(Element => Boolean) => Collection[Element]
filter:
(Transform[Element, Boolean]) => Collection[Element]
map[NewElement]:
(transform: Element => NewElement) => Collection[NewElement]
map[NewElement]:
(transform: Transform[Element, NewElement]) => Collection[NewElement]
map[NewElement]:
(Transform[Element, NewElement]) => Collection[NewElement]
}
Transform[Input, Output] {
Input => Output
}
Transform[Input, Output] {
(Input) => Output
}
Transform[Input, Output] {
(Input => Output)
}
Filter[Element] {
(Element => Boolean, Element[]) => Element[]
}
Filter[Element] {
(Transform[Element, Boolean], Element[]) => Element[]
}
Map[Input, Output] {
(Input => Output, Input[]) => Output[]
}
Map[Input, Output] {
(Transform[Input, Output], Input[]) => Output[]
}
interface Collection[Element] {
filter {
(Element => Boolean) => Collection[Element]
}
filter {
(Transform[Element, Boolean]) => Collection[Element]
}
[NewElement] map {
(transform: Element => NewElement) => Collection[NewElement]
}
[NewElement] map {
(transform: Transform[Element, NewElement]) => Collection[NewElement]
}
[NewElement] map {
(Transform[Element, NewElement]) => Collection[NewElement]
}
}
interface Collection[Element] {
filter {
(Element => Boolean) => Collection[Element]
}
filter {
(Transform[Element, Boolean]) => Collection[Element]
}
map[NewElement] {
(transform: Element => NewElement) => Collection[NewElement]
}
map[NewElement] {
(transform: Transform[Element, NewElement]) => Collection[NewElement]
}
map[NewElement] {
(Transform[Element, NewElement]) => Collection[NewElement]
}
}
Type arguments are enclosed in parentheses and are separated by commas. This syntax is influenced by JavaScript function notation. Since a generic type is a type constructor/function that takes types as arguments and returns a concrete type.
Transform(Input, Output) Input => Output
Transform(Input, Output) (Input) => Output
Transform(Input, Output) (Input => Output)
Filter(Element) (Element => Boolean, Element[]) => Element[]
Filter(Element) (Transform(Element, Boolean), Element[]) => Element[]
Map(Input, Output) (Input => Output, Input[]) => Output[]
Map(Input, Output) (Transform(Input, Output), Input[]) => Output[]
interface Collection(Element) {
filter(Element => Boolean) => Collection(Element)
filter(Transform(Element, Boolean)) => Collection(Element)
(NewElement) map(transform: Element => NewElement) => Collection(NewElement)
(NewElement) map(transform: Transform(Element, NewElement)) => Collection(NewElement)
(NewElement) map(Transform(Element, NewElement)) => Collection(NewElement)
}
interface Collection(Element) {
filter(Element => Boolean) => Collection(Element)
filter(Transform(Element, Boolean)) => Collection(Element)
map(NewElement)(transform: Element => NewElement) => Collection(NewElement)
map(NewElement)(transform: Transform(Element, NewElement)) => Collection(NewElement)
map(NewElement)(Transform(Element, NewElement)) => Collection(NewElement)
}
Transform(Input, Output): Input => Output
Transform(Input, Output): (Input) => Output
Transform(Input, Output): (Input => Output)
Filter(Element): (Element => Boolean, Element[]) => Element[]
Filter(Element): (Transform(Element, Boolean), Element[]) => Element[]
Map(Input, Output): (Input => Output, Input[]) => Output[]
Map(Input, Output): (Transform(Input, Output), Input[]) => Output[]
interface Collection(Element) {
filter: (Element => Boolean) => Collection(Element)
filter: (Transform(Element, Boolean)) => Collection(Element)
(NewElement) map: (transform: Element => NewElement) => Collection(NewElement)
(NewElement) map: (transform: Transform(Element, NewElement)) => Collection(NewElement)
(NewElement) map: (Transform(Element, NewElement)) => Collection(NewElement)
}
interface Collection(Element) {
filter: (Element => Boolean) => Collection(Element)
filter: (Transform(Element, Boolean)) => Collection(Element)
map(NewElement): (transform: Element => NewElement) => Collection(NewElement)
map(NewElement): (transform: Transform(Element, NewElement)) => Collection(NewElement)
map(NewElement): (Transform(Element, NewElement)) => Collection(NewElement)
}
Transform(Input, Output):
Input => Output
Transform(Input, Output):
(Input) => Output
Transform(Input, Output):
(Input => Output)
Filter(Element):
(Element => Boolean, Element[]) => Element[]
Filter(Element):
(Transform(Element, Boolean), Element[]) => Element[]
Map(Input, Output):
(Input => Output, Input[]) => Output[]
Map(Input, Output):
(Transform(Input, Output), Input[]) => Output[]
interface Collection(Element) {
filter:
(Element => Boolean) => Collection(Element)
filter:
(Transform(Element, Boolean)) => Collection(Element)
(NewElement) map:
(transform: Element => NewElement) => Collection(NewElement)
(NewElement) map:
(transform: Transform(Element, NewElement)) => Collection(NewElement)
(NewElement) map:
(Transform(Element, NewElement)) => Collection(NewElement)
}
interface Collection(Element) {
filter:
(Element => Boolean) => Collection(Element)
filter:
(Transform(Element, Boolean)) => Collection(Element)
map(NewElement):
(transform: Element => NewElement) => Collection(NewElement)
map(NewElement):
(transform: Transform(Element, NewElement)) => Collection(NewElement)
map(NewElement):
(Transform(Element, NewElement)) => Collection(NewElement)
}
Transform(Input, Output) {
Input => Output
}
Transform(Input, Output) {
(Input) => Output
}
Transform(Input, Output) {
(Input => Output)
}
Filter(Element) {
(Element => Boolean, Element[]) => Element[]
}
Filter(Element) {
(Transform(Element, Boolean), Element[]) => Element[]
}
Map(Input, Output) {
(Input => Output, Input[]) => Output[]
}
Map(Input, Output) {
(Transform(Input, Output), Input[]) => Output[]
}
interface Collection(Element) {
filter {
(Element => Boolean) => Collection(Element)
}
filter {
(Transform(Element, Boolean)) => Collection(Element)
}
(NewElement) map {
(transform: Element => NewElement) => Collection(NewElement)
}
(NewElement) map {
(transform: Transform(Element, NewElement)) => Collection(NewElement)
}
(NewElement) map {
(Transform(Element, NewElement)) => Collection(NewElement)
}
}
interface Collection(Element) {
filter {
(Element => Boolean) => Collection(Element)
}
filter {
(Transform(Element, Boolean)) => Collection(Element)
}
map(NewElement) {
(transform: Element => NewElement) => Collection(NewElement)
}
map(NewElement) {
(transform: Transform(Element, NewElement)) => Collection(NewElement)
}
map(NewElement) {
(Transform(Element, NewElement)) => Collection(NewElement)
}
}
Type arguments are written without backets and are separated by whitespace. This syntax is influenced by Elm and Haskell.
Transform Input Output Input => Output
Transform Input Output (Input) => Output
Transform Input Output (Input => Output)
Filter Element (Element => Boolean, Element[]) => Element[]
Filter Element (Transform Element Boolean, Element[]) => Element[]
Map Input Output (Input => Output, Input[]) => Output[]
Map Input Output (Transform Input Output, Input[]) => Output[]
interface Collection Element {
filter(Element => Boolean) => Collection Element
filter(Transform Element Boolean) => Collection Element
NewElement map(transform: Element => NewElement) => Collection NewElement
NewElement map(transform: Transform Element NewElement) => Collection NewElement
NewElement map(Transform Element NewElement) => Collection NewElement
}
interface Collection Element {
filter(Element => Boolean) => Collection Element
filter(Transform Element Boolean) => Collection Element
map NewElement(transform: Element => NewElement) => Collection NewElement
map NewElement(transform: Transform Element NewElement) => Collection NewElement
map NewElement(Transform Element NewElement) => Collection NewElement
}
Transform Input Output: Input => Output
Transform Input Output: (Input) => Output
Transform Input Output: (Input => Output)
Filter Element: (Element => Boolean, Element[]) => Element[]
Filter Element: (Transform Element Boolean, Element[]) => Element[]
Map Input Output: (Input => Output, Input[]) => Output[]
Map Input Output: (Transform Input Output, Input[]) => Output[]
interface Collection Element {
filter: (Element => Boolean) => Collection Element
filter: (Transform Element Boolean) => Collection Element
NewElement map: (transform: Element => NewElement) => Collection NewElement
NewElement map: (transform: Transform Element NewElement) => Collection NewElement
NewElement map: (Transform Element NewElement) => Collection NewElement
}
interface Collection Element {
filter: (Element => Boolean) => Collection Element
filter: (Transform Element Boolean) => Collection Element
map NewElement: (transform: Element => NewElement) => Collection NewElement
map NewElement: (transform: Transform Element NewElement) => Collection NewElement
map NewElement: (Transform Element NewElement) => Collection NewElement
}
Transform Input Output:
Input => Output
Transform Input Output:
(Input) => Output
Transform Input Output:
(Input => Output)
Filter Element:
(Element => Boolean, Element[]) => Element[]
Filter Element:
(Transform Element Boolean, Element[]) => Element[]
Map Input Output:
(Input => Output, Input[]) => Output[]
Map Input Output:
(Transform Input Output, Input[]) => Output[]
interface Collection Element {
filter:
(Element => Boolean) => Collection Element
filter:
(Transform Element Boolean) => Collection Element
NewElement map:
(transform: Element => NewElement) => Collection NewElement
NewElement map:
(transform: Transform Element NewElement) => Collection NewElement
NewElement map:
(Transform Element NewElement) => Collection NewElement
}
interface Collection Element {
filter:
(Element => Boolean) => Collection Element
filter:
(Transform Element Boolean) => Collection Element
map NewElement:
(transform: Element => NewElement) => Collection NewElement
map NewElement:
(transform: Transform Element NewElement) => Collection NewElement
map NewElement:
(Transform Element NewElement) => Collection NewElement
}
Transform Input Output {
Input => Output
}
Transform Input Output {
(Input) => Output
}
Transform Input Output {
(Input => Output)
}
Filter Element {
(Element => Boolean, Element[]) => Element[]
}
Filter Element {
(Transform Element Boolean, Element[]) => Element[]
}
Map Input Output {
(Input => Output, Input[]) => Output[]
}
Map Input Output {
(Transform Input Output, Input[]) => Output[]
}
interface Collection Element {
filter {
(Element => Boolean) => Collection Element
}
filter {
(Transform Element Boolean) => Collection Element
}
NewElement map {
(transform: Element => NewElement) => Collection NewElement
}
NewElement map {
(transform: Transform Element NewElement) => Collection NewElement
}
NewElement map {
(Transform Element NewElement) => Collection NewElement
}
}
interface Collection Element {
filter {
(Element => Boolean) => Collection Element
}
filter {
(Transform Element Boolean) => Collection Element
}
map NewElement {
(transform: Element => NewElement) => Collection NewElement
}
map NewElement {
(transform: Transform Element NewElement) => Collection NewElement
}
map NewElement {
(Transform Element NewElement) => Collection NewElement
}
}
It is hard to compare syntax impersonal and objectively. Opinions might differ on readability and the question if familiarity of a syntax is an advantage or an disadvantage. Thus, I mainly collect opinions as quotations. I will add opinions that arise in further discussion. Please tell me if any opinion or aspect is missing or if something is not quite appropriate.
Similar to C++ templates and Java, C# and TypeScript generics.
Thousands of C++/Java/C# developers are currently being converted to JS developers, you all know that. Students in (high)schools study the C++/Java/C#. I would recommend to make sure these people can read rtype easily without browsing the documentation (JSDoc is quite readable, btw, thus popular). [...] TypeScript syntax is readable to most developers -- koresar
The problem is that the <> syntax isn't consistent between languages anyway. Look at Java, C#, Kotlin, Rust, etc. all of them are doing it differently. -- unknown reddit user
TypeScript's generics in function signatures, and the TypeScript syntax feels like an assault on my senses -- ericelliott
I think there's something about the angle brackets that drives me crazy. Reminds me of C++ templates or something (which I have bad memories of). -- ericelliott
👎 For angle brackets -- BerkeleyTrue
Java's approach is saying "f*ck it", let's just have stuff like
method<String>(), butinstance.<String>method()at use-site; and at declaration-site (different issue) it's even more different withclass Box<T>vs.<T> void foo(T t). -- unknown reddit user
- common syntax in many languages
-
< (Unicode Character 'LESS-THAN SIGN') and > (Unicode Character 'GREATER-THAN SIGN') used as brackets are harder to read, since they are no real brackets. The real angle brackets are 〈 (Unicode Character 'LEFT ANGLE BRACKET') and 〉 (Unicode Character 'RIGHT ANGLE BRACKET').
-
parsing issues
Using
<>for generics is a historical accident. If you look at all the languages which adopted it, all of them need horrible hacks to work around the intrinsic issues. -- unknown reddit userDo you have any examples where they had to work around these parsing issues due to using <> for generics?
C# and Kotlin for example just keep reading after the
<and have a huge list of tokens to determine whether it's a comparison, Generics, or if they need to keep reading further. They basically need unlimited lookahead, and if they figure it out, they rollback the parser state and parse it again, with more information. -- unknown reddit userIn return you avoid the compiler having to look ahead a little during parsing. Is that really a good trade? I don't.
If even the parser has trouble reading, people will very likely also have trouble with comprehending things. -- unknown reddit user
In case of a single-parameter-function as concrete type argument readability is decreased with angle brackets notation due to the fact that a closing angle bracket is used as arrow head:
MyInterface<String => String> // angle bracket syntax MyInterface<(String => String)> // angle bracket syntax, IMO parentheses don't increase readability in this case MyInterface (String => String) // whitespace syntax (type argument has to be enclosed in parentheses, otherwise it would be equal to `(MyInterface String) => String` MyInterface(String => String) // parentheses/function syntax; Note: Could also be written like whitespace syntax in this case: `MyInterface (String => String)`
-- maiermic
map<t, u>(fn: (x: t) => u, list: t[]) => u[];IMO, this is even worse than the C++ & Java versions because of the arrow functions. Note how the closing angle bracket for the type parameter declaration looks visually very similar to the arrows in the arrow functions. My brain tries to match them up.My brain also struggles with the groupings of type parameters and function parameters, mostly lost in the syntax noise.
[...]
interface StringStringThing: MyInterface<String => String>
Which visually looks a bit like this:
interface StringStringThing: MyInterface<String => // wtf is the rest of that nonsense?
-- ericelliott
Similar to Scala generics
Java's approach is saying "f*ck it", let's just have stuff like
method<String>(), butinstance.<String>method()at use-site; and at declaration-site (different issue) it's even more different withclass Box<T>vs.<T> void foo(T t). In Scala, everything is regular and consistent. The[]are always at the same place, everywhere. -- unknown reddit user
- consistency: every time you see
[](in rtype) a generic type is described
- every time you see
[]in JavaScript an object property or array element is accessed. - conflicts with current rtype array syntax
-
might be resolved by writting
[Number]instead ofNumber[]We could write
Array[T]instead ofT[]in rtype. We might use[T]as short version ofArray[T]. One benefit of writing the generic type in brackets is better readablity of type expressions:(Number | String)[]vs.[Number | String]orArray[Number | String]-- maiermic
-
Similar to a regular JavaScript function.
Advantages:
- it looks similar to a function declaration
- it behaves similar to a function declaration: defines a type constructor (= function) that takes types as parameters and returns a type
-- maiermic
Disadvantages:
- it looks similar to a function declaration
- it behaves similar to a function declaration: defines a type constructor (= function) that takes types as parameters and returns a type
Sorry for trolling like messages. That's my personal opinion. We should lean towards readability. Using parenthesis for everything damages readability significantly. (One of the reasons to not like Lisp is the parenthesis bloat.) -- koresar
-
type constructor are functions
Generic types like
Mapare not just type names. They are type constructors:type constructor is a feature of a typed formal language that builds new types from old ones Wikipedia - Type constructor
That means a type constructor is not a type itself, but it describes a class of types. -- maiermic
A type constructor is more like a function that returns a type:
// type constructor Map function Map(t: Type, u: Type): Type { return ((x: t) => u, list: t[]) => u[] }-- maiermic
- a type constructor might get confused with a regular function
- if types are inferred, type arguments are passed implicitly, which is not possible in regular JavaScript functions
Similar to Elm and Haskell.
- fewer characters (no special brackets) are needed
- reusable/composable: due to curring, partial function application of type constructors is possible
-
whitespace syntax introduces currying. Most JavaScript developers are not familiar with this concept of function notation.
Haskell is known but an exotic language yet. For C++/Java/C# developers it is counter intuitive that the space symbol have a special syntactic meaning. Frankly speaking, I still do not understand what's that:
Transform t u: (x: t) => u. -- koresar