Many languages used [] to add syntax for collection literals ([1, 2, 3]) or array lookup (array[0]), adding pointless complexity to the language for very little benefit – as such built-in syntax usually becomes dead weight a few years down the road, as the preferred choice of data structure implementation evolves.
It sounds like we may have very different language experiences. An array of contiguous memory is such a fundamental type in systems programming that I can’t imagine not having convenient access syntax for it (I have no strong feelings on init or literals), of which [] is the most common convention. “Usually becomes dead weight a few years down the road” seems like a gross overstatement - preferred data structures don’t change that frequently or often, the only I can personally think of is largely moving away from linked lists in my own work.
I would also take this point more as a reason for operator overloading rather than a reason not to use [] in data structure syntax.
A few examples: Java abandoned arrays, never integrated them with collections in 1.2, let alone generics in 1.5. Scala switched from List to Vector – which was only possible because they didn’t bless lists with special syntax. Rust devs pretty much immediately abandoned arrays/slices in favor of Vec.
“abandoning slices for Vec” doesn’t make sense. It’s as if you said “filesystem users abandoned files for directories”. Slices and Vec are borrowed and owned variants of contiguous memory, and you need to use them as appropriate for the context. Slices are the lowest common denominator and aren’t going anywhere.
Besides, there’s vec![] literal and it can be indexed with []. [] is overloadable with ops::Index trait, so any container du jour that can produce a reference can use it.
slightly later people push for [] being extended to arbitrary types, rendering the point of knowing what [] does moot.
How ops::Index can be used is somewhat constrained in Rust, because it’s index method is required to return a reference. Due to borrowing/ownership semantics you cannot create a temporary value and return a reference to it. Consequently, the ops::Index trait can only really be used for container-like datastructures.
Of course, you cannot know the time complexity, e.g. a map could be a hash map or binary tree. But the same is true for e.g. a get method.
How ops::Index can be used is somewhat constrained in Rust, because it’s index method is required to return a reference. Due to borrowing/ownership semantics you cannot create a temporary value and return a reference to it. Consequently, the ops::Index trait can only really be used for container-like datastructures.
I think “get me a value from position x” and “create a reference to position x” are so fundamentally different operations that it’s fair to say that trying to handle them bot with [] is a mistake.
Just use two different methods for doing two different things.
But the same is true for e.g. a get method.
Which is exactly my point: This special [] syntax does not tell you more than any other method, so there is no reason why it should be special.
This special [] syntax does not tell you more than any other method, so there is no reason why it should be special.
Ah, it struck me just now that it’s basically a general argument against operators (and operator overloading). So I think it is moot as a specific argument against [] in particular, more than any other ops. IOW, exactly the same can be said about + etc.:
Let’s paraphrase: “The special + syntax does not tell you more than any other method, so there is no reason why it should be special. [We can as well just write it as a regular method: a.plus(b)]”
Preempting an argument about frequency of use, I’m pretty sure [] is used much more often in general programming than /… (except maybe in the “niche domain DSL of methematicians and physicists”, but then they use 2+ -dimensional matrices & vectors a lot, and not many languages give them those anyway.)
Yup, the Java situation was unfortunate. Everything about arrays to collections to generics to collection literals was an interesting transition, but I have trouble drawing too many forward-looking language design lessons from it, except that I do think there’s value in allowing overload for a limited subset of operators.
Scala switched from List to Vector – which was only possible because they didn’t bless lists with special syntax
They could’ve blessed it with special syntax and allowed operator overloading. They didn’t need to because the apply function fulfilled a similar role. You can debate which style you preferred - apply function versus allowing [] and overloading, but saying it was “only possible” because they didn’t bless lists with special syntax is overstating it.
Rust devs pretty much immediately abandoned arrays/slices in favor of Vec.
Saying arrays/slices were abandoned is also overstating it. arrays/slices are a fundamental type, Vec was built on them, and [] overloading is supported via the index trait, so I fail to see the point in this example.
Rust devs pretty much immediately abandoned arrays/slices in favor of Vec.
Huh? I can’t ever remember a time when arrays were more prominently used instead of Vec. They are just two totally different things. And to lob slices in there is just strange… Nobody has abandoned slices. Slices and Vec are complementary.
Arrays are perhaps uncommon enough to not deserve their own [...] syntax, but writing &[...]is super common in my experience, and it winds up having a nice consistency with the array syntax (with a dash of type coercion).
Personally, I avoid [...] and &[...] like the plague
Please don’t do that, especially if you are writing libraries. If you functions/methods take &Vec<T> and all I have is a &[T], it means that I have to create an intermediate Vec so that I can pass it to your function.
There is only one good scenario to ask a (mutable) reference to a Vec (&mut Vec), namely when a function/method needs to expand it (add new items, as opposed to get or modify items).
I’m downvoting this comment as “troll”, as it cannot be discussed on merit, trying to argue with it could only lead to a heated quarell, and it doesn’t add anything valuable to the discussion (it’s purely a pout). I’m writing this explanatory comment because generally I see the article and your discussion interesting and civil, giving you a reasonable “respect credit”.
Are you engaging with the Rust RFC process to push for your preferred syntax? Because if not, comments like ‘I’ll purposefully make my libraries less efficient until I get better syntax’ are trolling.
You’re arguing for creating worse apis because, and I quote: “Language designers forced my hand here. They could provide better syntax, and then I’ll reconsider.”?
This reads like some C programmer rants I’ve had with at work where they would argue for/against (never consistently either) that they should be able to free() passed in data or allocate whenever they wanted to and return back and that is why they avoided pointers or references in their code because it was “ugly or hard to understand”.
The languages type system isn’t a UI, its a type system, avoiding things like * or & because they’re not pretty seems to avoid the whole point of having the type system and things like mut/slices/vectors/etc….
If you prefer to use mut all over the place for the apis you create using rust that’s fine. But don’t be surprised when most programmers view the perspective as oddly idealogical as requiring callers to do more work or even mean your apis can’t be used due to the memory allocation as a very wrong point of view.
I’m objecting to the syntactic special-casing of arrays and slices at pretty much every place in Rust. I find that some lonesome [ at column 110 in a type signature somewhere is an incredibly subtle way to indicate a context switch from “this one value accepts/requires/impls …” to “an arbitrary amount of values accept/require/impl …”.
Introducing Arr and Slc types (or whatever arbitrary abbreviation Rust people can come up with) and replacing the literals with macros would go a long way at addressing these issues.
With const generics, I’m pretty sure most mechanics for array could even be largely implemented as a library.
Nothing of this has has an impact on the type system.
Gotcha, I misunderstood then. My bad thanks for the clarification!
As a devout Idris user, I’m of the opinion this kinda stuff should move to the type system. But I’m a bit of a crazy person about Type driven development. Rust is “ok” for day to day but could definitely use something like Higher Kinded Types etc…. Its acceptable though.
but array literals and indexing are pretty standard. TFA is hyperbolic about their uselessness
I’m not saying that they are useless – I just consider it a benefit that using [] for generics makes it close to impossible to use [] for special-cased syntax “conveniences”.
D uses !() instead.
I don’t think D’s approach is good. It feels like the basically understood the problem, but their solution is pretty much as ugly as the problem it tried to solve…
Are there any keyboards with «»? Seems like everyone types them with copy-paste, smart quotes, Alt+code in Windows, and (for unix enthusiasts) the Compose key…
Bet you a chocolate bar there’s an easy way to type them on French layouts. :)
Edit: I lose this bet! There are nice mechanisms for typing guillemets on X11 and OSX but it seems there’s nothing sensible on Windows!? I found a couple of French language forums with people saying your best bet on Windows with an azerty layout is still to type alt+174 / alt+175
Yeah the French keyboard layout on Windows is truly awful. There is no way to type the half of the special characters you need in regular French without using terrible hacks like Alt+123, even for ubiquitous characters like ’ or É. The Linux default AZERTY layout for French is way better, and the “OSS” variant is miles ahead (you can type everything you need in French on this one).
Also, system-wide automatic substitutions that macOS and iOS do are pretty good for French. Software like Microsoft Word also substitutes some characters automatically but it’s not system-wide.
There were people in D who wanted to use «», but indeed it was shot down for being painful for Americans to type.
But I’m glad it did get shot down becase really, why would we use that? Is it just because C++ uses <> and «» looks kinda like it without the same technical problems…? Or is there some deeper meaning beyond that? Ditto with ⟨ ⟩.
What I like so much about D’s decision to reuse plain () (with ! to indicate the presence of compile-time arguments) is that it questions that fundamental assumption that it should look different… notice instead of “generics” or “templates” or whatever, I just call it “compile-time arguments” - thanks to the syntax being so similar it lets us rethink how special the concept itself really needs to be.
The title of the article is : “Language designers, stop using <> for generics “
But even with Polymorpic types, I’d still rather go with Type Application, rather than a tuple (,)
data Measure a b = Measure { quantity :: a , unit :: b }
value :: MyFancyType Int InternationalSystem -- here notice the type application
value = Measure 5 SquareMeter
And although I don’t use them, with the common language extensions TypeOperators or Generics , you would have the available type operator & (like Int & Char) or :*: (like Int :*: Char)
Yes and they use [] for lists and {} for records, that’s not the point. Type application just uses spaces, with () used to indicate precedence if necessary: Maybe (Maybe a). I think this is by far the easiest to read.
The D language uses plain (). In the definition, you add a compile-time parameter list before the run-time parameter list, and they work very similarly. void foo(T)(T t); has a type T as a compile time param, then a runtime value param t of type T.
To call it, well, many cases are implicit: foo(0); or foo("") bot just work, or you can explicitly call them by using the ! to indicate the presence of a compile-time parameter list: foo!(int)(5). (the parens around a compile time arg list can be omitted if the argument is a single, simple item, so you frequently see foo!int(5) in D code too.)
It avoids all the <> ugliness and I really like the syntax similarity between CT and RT params; it is no longer a wholly separate concept and I think that semi-merging of compile time and run time code and concepts is one of D’s major strengths.
as for their final thing about [] lol i like the array stuff but indeed it could just as well use () for that same stuff too, so eh i kinda get their point i just like D’s way
I don’t think D’s approach is good. It feels like the basically understood the problem, but their solution is pretty much as ugly as the problem it tried to solve…
Well, that’s extremely subjective so yeah. When the proposed the drop parens thing I at first was aghast and voted against it since I thought it was hideous at the time. But I came around pretty quickly and like it quite a lot now.
I could probably get used to a foo[int](5) too though.
At the bottom, you suggest that collection literals are an abuse of square brackets. At first, that really surprised me (since I use those all the time). Now that I’ve thought about it a bit more, I can see that there’s some logic in not having them, since they could make a language less future-proof. For example, Python has a lot of literals for different data types. Outside of those types, anything you make yourself is going to have uglier syntax. Scheme has a #() notation for vectors (which I think is implemented using reader macros). Is the configurable reader macro approach the most future-proof, or does it encourage Balkanizing in libraries? What languages today don’t have some kind of array literal?
I agree with the body of your article wholeheartedly, but I’m still unsure of the ending.
You may or may not already know, but Nim is an example of a language that uses [] for generics — and in fact for (overloadable) array access and construction as well. One place where this led to ambiguity and thus required some extra convoluted irregularity (unfortunately) is in the “method call syntax” for procedures. As explained in the manual, calling a generic procedure with method syntax must thus be written with an extra colon (:), for example: object.method[:T](args)
I found this argument compelling enough to decide to switch the syntax of the toy language I sporadically work on from angle brackets to square brackets.
By not having gotten far enough along in my implementation of the language to have done anything interesting with array literals or indexing :)
Scala uses the ()-based method call syntax for indexing into an array-like data structure, which I’ve always found a little bit unusual, but it certainly works. And even in languages that do have traditional [] indexing syntax, it’s pretty common to call a method like .get to (perhaps more safely) index into the data structure, so making that mandatory isn’t too outlandish. As far as creating literal arrays and array-like sequences, Rust offers a compelling example. vec![a, b, c] is the standard way to create a vector literal - but this is a macro that desugars to something like
In fact, this macro treats your exact choice of delimiter as unimportant - vec!{a, b, c} or vec!(a, b c) work just as well. So having some sort of text tag like vec! in the syntax for literal construction seems unobtrusive enough, and that exact tag can vary to indicate what sort of literal is desired (maybe arr[...] for arrays, vec[..] for vectors, deq[...] for a deque?).
Of course this is a toy language that only I use, so it’s very easy to make arbitrary design changes based on blog posts I happen to have read :)
So having some sort of text tag like vec! in the syntax for literal construction seems unobtrusive enough, and that exact tag can vary to indicate what sort of literal is desired (maybe arr[…] for arrays, vec[..] for vectors, deq[…] for a deque?).
But how is this better than just doing Arr(...), Vec(...), Deq(...)?
If those are macros that can take any number of arguments, it’s not, just slightly-different syntax. If those are functions, then your language’s type system needs to deal with functions that can take arbitrarily-many arguments.
I agree and will do the same. I wasn’t at the stage of implementing generics yet. Yet I did do some research and looked at what other languages were using as syntax for generics. This article solidified me on that future syntax of [].
Somehow I am reading this as: somebody please make a programming font where <,> have the height of (,),[,],{,}. But maybe that will look too terrible for comparison.
Like hwayne said, there’s already the ⟨ ⟩ symbols, which are actually pretty nice. We don’t have a key for them, but you could remap the < > keys or set up a replacement in your editor for certain filetypes.
I think that if someone designed a language the relied on characters outside ASCII the reaction would be incredulous outrage from existing developers. It doesn’t matter how easy you make it to remap keys, the ideal of code == ASCII is so deeply embedded as to be a dogma.
(preemtive rebuttal: APL doesn’t count, there are already 2 or three successors that are ASCII-only).
It could just be an alternative to the <> for people who disliked them. Many languages already support UTF-8 identifiers (Go and Raku, off the top of my head).
I’m not familiar enough with Raku to know if the UTF characters are required for the language, or a just nicer-looking alternatives to the “standard” ASCII variants.
This is really annoying to me because there are a lot of languages that have an ascii replacement for ∀, like forall and always and \A when it would be so much nicer to be able to just drop ∀ in there and have the parser know what I mean.
Spending a lot of time in functional languages has made articles like this really odd for me, like they are from another planet. I can’t imagine something like Haskell without lists prevalent everywhere and the square-bracket syntax. On the other hand Haskell has the approximate equivalent of generics without needing to use any bracket syntax at all.
Are there any nice, distinct pairs of brackets outside of the ASCII subset of Unicode that could be put to use?
There are other choices. I used « » as delimiters (not for generics) in a simple declaration syntax in 1991. Most languages support Unicode to some degree now, but I don’t think they take enough advantage of it. I just see people using Greek letters for math constants and emoji for variable names.
It’s common in julia to use unicode symbols as operators and elsewhere to make code look more like math. The base language defines a bunch of these for comparison, function composition, constants, etc.
Using [] for generics instead of <> shuts down this possibility for good, and encourages the use of standard method call syntax for these usecases instead:
Array(1, 2, 3) /* instead of */ [1, 2, 3]
someList(0) /* instead of */ someList[0]
array(0) = 23.42 /* instead of */ array[0] = 23.42
map("name") = "Joe" /* instead of */ map["name"] = "Joe"
array(0) = 23.42 /* instead of */ array[0] = 23.42 feels like a very weak case to me.
This is not how standard method calls work in most languages, so either this is just another form of syntactic sugar which feels confusing due to overloading with how real functions and methods work, or perhaps some tortured runtime implementation where the array function returns something that is simultaneously a getter and setter depending on how it’s used?
If this was the point they really wanted to make perhaps array.set(0, 23.42) /* instead of */ array[0] = 23.42 would be better.
You are right, but I just couldn’t be bothered to deal with people having a meltdown over having to write 3 more characters in one case. So I kept it simple.
Fair enough :)
You might be interested in a side project of mine, Forest. It’s a functional programming language that targets Wasm, but the notable thing for you might be that I am trying to make syntax an end-user concern.
The basic principle is that a syntax is a pair of reversable parsing and printing (as in, print -> parse -> print gets the same result for any module). I am aiming to provide tooling that runs a FUSE powered FS that projects the code into your preferred syntax for editing. This would enable developers working on the same project to use different syntaxes without needing any consensus (except around which syntax is in source control, but tooling means that’s not hugely important).
Part of the rationale for this was trying to separate the language’s core semantics from syntax, so that conversations like these can happen and change the syntax people use without having a huge kerfuffle over backwards compatibility.
Obviously this creates quite a few problems as well, but more and more I’m growing convinced that arguments like this over syntax would be much less important if we could just pick how we wanted our code to look.
Anyway, while Forest won’t have an authoritative syntax, it’s nice to think that if you used it in future you would hopefully just be able to use a syntax with square brackets for generics.
I’m growing convinced that arguments like this over syntax would be much less important if we could just pick how we wanted our code to look.
I’m approaching this from the other side, I guess: I believe that arguments over syntax should be less important by improving the syntax to something that has actively been designed and engineered.
This is in contrast to today’s standard design approach, which is basically creating a language by LARPing some 1960’s “design held together together with bubble gum, spit and baling wire” event, where the biggest inspiration is to speculate how C would look in a parallel universe in which BCPL was invented on a Thursday, and not a Tuesday.
I believe that arguments over syntax should be less important by improving the syntax to something that has actively been designed and engineered.
Absolutely agree, I hope that having empowering users to play with syntax speeds up that process dramatically :)
This is in contrast to today’s standard design approach, which is basically creating a language by LARPing some 1960’s “design held together together with bubble gum, spit and baling wire” event, where the biggest inspiration is to speculate how C would look in a parallel universe in which BCPL was invented on a Thursday, and not a Tuesday.
For Inko I went with !(), inspired by D. This means you’d write Array!(Integer) instead of Array<Integer>. It was a bit easier parsing wise since square brackets are already used for array/map indexing (e.g. some_list_of_numbers[10]), making it difficult to figure out what Foo[Bar] is supposed to mean (what if Foo is an Array and Bar a constant containing the index?). !() takes some getting used to, but I’ve grown to quite like it.
Using square brackets for indexing doesn’t require blessing any data structure in particular. Using them for data structure creation does, of course, but that’s different.
The main point is that it’s still a bad idea to make a single function like “index” special and give it custom syntax, because there is nothing special about it.
It sounds like we may have very different language experiences. An array of contiguous memory is such a fundamental type in systems programming that I can’t imagine not having convenient access syntax for it (I have no strong feelings on init or literals), of which [] is the most common convention. “Usually becomes dead weight a few years down the road” seems like a gross overstatement - preferred data structures don’t change that frequently or often, the only I can personally think of is largely moving away from linked lists in my own work.
I would also take this point more as a reason for operator overloading rather than a reason not to use [] in data structure syntax.
A few examples: Java abandoned arrays, never integrated them with collections in 1.2, let alone generics in 1.5. Scala switched from
List
toVector
– which was only possible because they didn’t bless lists with special syntax. Rust devs pretty much immediately abandoned arrays/slices in favor ofVec
.“abandoning slices for
Vec
” doesn’t make sense. It’s as if you said “filesystem users abandoned files for directories”. Slices andVec
are borrowed and owned variants of contiguous memory, and you need to use them as appropriate for the context. Slices are the lowest common denominator and aren’t going anywhere.Besides, there’s
vec![]
literal and it can be indexed with[]
.[]
is overloadable withops::Index
trait, so any container du jour that can produce a reference can use it.Yeah, the usual series of events is:
[]
lookup restricted to some specific type[]
being extended to arbitrary types, rendering the point of knowing what[]
does moot.So in the end it just isn’t worth bothering with
[]
– just use functions.How
ops::Index
can be used is somewhat constrained in Rust, because it’sindex
method is required to return a reference. Due to borrowing/ownership semantics you cannot create a temporary value and return a reference to it. Consequently, theops::Index
trait can only really be used for container-like datastructures.Of course, you cannot know the time complexity, e.g. a map could be a hash map or binary tree. But the same is true for e.g. a
get
method.I think “get me a value from position x” and “create a reference to position x” are so fundamentally different operations that it’s fair to say that trying to handle them bot with
[]
is a mistake.Just use two different methods for doing two different things.
Which is exactly my point: This special
[]
syntax does not tell you more than any other method, so there is no reason why it should be special.Ah, it struck me just now that it’s basically a general argument against operators (and operator overloading). So I think it is moot as a specific argument against
[]
in particular, more than any other ops. IOW, exactly the same can be said about+
etc.:Let’s paraphrase: “The special
+
syntax does not tell you more than any other method, so there is no reason why it should be special. [We can as well just write it as a regular method:a.plus(b)
]”Preempting an argument about frequency of use, I’m pretty sure
[]
is used much more often in general programming than/
… (except maybe in the “niche domain DSL of methematicians and physicists”, but then they use 2+ -dimensional matrices & vectors a lot, and not many languages give them those anyway.)The core difference is that
[]
has a replacement that as concise as the original, which may not be true for other operations.That’s a broken-window approach I’m not an adherent of.
And for many of them the same treatment applies.
For instance, I replaced legacy unary operators like
!
or~
with a method call and it’s much more readable:You don’t have to first read through the whole predicate and then backtrack to the start to figure out what’s actually being negated.
That leaves binary operators
+
,-
,/
and*
.Compared to a situation were random things are special-cased all over the place, I can live with that.
Yup, the Java situation was unfortunate. Everything about arrays to collections to generics to collection literals was an interesting transition, but I have trouble drawing too many forward-looking language design lessons from it, except that I do think there’s value in allowing overload for a limited subset of operators.
They could’ve blessed it with special syntax and allowed operator overloading. They didn’t need to because the apply function fulfilled a similar role. You can debate which style you preferred - apply function versus allowing [] and overloading, but saying it was “only possible” because they didn’t bless lists with special syntax is overstating it.
Saying arrays/slices were abandoned is also overstating it. arrays/slices are a fundamental type, Vec was built on them, and [] overloading is supported via the index trait, so I fail to see the point in this example.
Huh? I can’t ever remember a time when arrays were more prominently used instead of
Vec
. They are just two totally different things. And to lob slices in there is just strange… Nobody has abandoned slices. Slices andVec
are complementary.Arrays are perhaps uncommon enough to not deserve their own
[...]
syntax, but writing&[...]
is super common in my experience, and it winds up having a nice consistency with the array syntax (with a dash of type coercion).[Comment from banned user removed]
Please don’t do that, especially if you are writing libraries. If you functions/methods take
&Vec<T>
and all I have is a&[T]
, it means that I have to create an intermediateVec
so that I can pass it to your function.There is only one good scenario to ask a (mutable) reference to a
Vec
(&mut Vec
), namely when a function/method needs to expand it (add new items, as opposed to get or modify items).[Comment from banned user removed]
I’m downvoting this comment as “troll”, as it cannot be discussed on merit, trying to argue with it could only lead to a heated quarell, and it doesn’t add anything valuable to the discussion (it’s purely a pout). I’m writing this explanatory comment because generally I see the article and your discussion interesting and civil, giving you a reasonable “respect credit”.
[Comment from banned user removed]
Are you engaging with the Rust RFC process to push for your preferred syntax? Because if not, comments like ‘I’ll purposefully make my libraries less efficient until I get better syntax’ are trolling.
You’re arguing for creating worse apis because, and I quote: “Language designers forced my hand here. They could provide better syntax, and then I’ll reconsider.”?
This reads like some C programmer rants I’ve had with at work where they would argue for/against (never consistently either) that they should be able to free() passed in data or allocate whenever they wanted to and return back and that is why they avoided pointers or references in their code because it was “ugly or hard to understand”.
The languages type system isn’t a UI, its a type system, avoiding things like * or & because they’re not pretty seems to avoid the whole point of having the type system and things like mut/slices/vectors/etc….
If you prefer to use mut all over the place for the apis you create using rust that’s fine. But don’t be surprised when most programmers view the perspective as oddly idealogical as requiring callers to do more work or even mean your apis can’t be used due to the memory allocation as a very wrong point of view.
[Comment from banned user removed]
How so? You described the Rust type system as a user interface.
Your equivalence of the Rust type system as bad UI and certain C programmers doing the same thing with * and & and producing horrible outcomes.
I’m objecting to the syntactic special-casing of arrays and slices at pretty much every place in Rust. I find that some lonesome
[
at column 110 in a type signature somewhere is an incredibly subtle way to indicate a context switch from “this one value accepts/requires/impls …” to “an arbitrary amount of values accept/require/impl …”.Introducing
Arr
andSlc
types (or whatever arbitrary abbreviation Rust people can come up with) and replacing the literals with macros would go a long way at addressing these issues.With const generics, I’m pretty sure most mechanics for
array
could even be largely implemented as a library.Nothing of this has has an impact on the type system.
Gotcha, I misunderstood then. My bad thanks for the clarification!
As a devout Idris user, I’m of the opinion this kinda stuff should move to the type system. But I’m a bit of a crazy person about Type driven development. Rust is “ok” for day to day but could definitely use something like Higher Kinded Types etc…. Its acceptable though.
I think the proper response to this comment is: WAT?
Slices are everywhere.
I can’t possibly imagine how one could use Rust while actively avoiding slices.
[Comment from banned user removed]
Uh, no, I don’t think so. Not even remotely comparable. I guess you must be trolling.
I agree
<>
is problematic, but array literals and indexing are pretty standard. TFA is hyperbolic about their uselessness.D uses
!()
instead.The D guys have done some thinking.
I’m not saying that they are useless – I just consider it a benefit that using
[]
for generics makes it close to impossible to use[]
for special-cased syntax “conveniences”.I don’t think D’s approach is good. It feels like the basically understood the problem, but their solution is pretty much as ugly as the problem it tried to solve…
This is one of those neat cases where PLT is shaped by American keyboards. We wouldn’t have this problem if we had «», or ⟨ ⟩, or ⦃ ⦄, or 𓁝𓁜.
Are there any keyboards with «»? Seems like everyone types them with copy-paste, smart quotes, Alt+code in Windows, and (for unix enthusiasts) the Compose key…
Bet you a chocolate bar there’s an easy way to type them on French layouts. :)
Edit: I lose this bet! There are nice mechanisms for typing guillemets on X11 and OSX but it seems there’s nothing sensible on Windows!? I found a couple of French language forums with people saying your best bet on Windows with an azerty layout is still to type alt+174 / alt+175
Yeah the French keyboard layout on Windows is truly awful. There is no way to type the half of the special characters you need in regular French without using terrible hacks like Alt+123, even for ubiquitous characters like ’ or É. The Linux default AZERTY layout for French is way better, and the “OSS” variant is miles ahead (you can type everything you need in French on this one).
Also, system-wide automatic substitutions that macOS and iOS do are pretty good for French. Software like Microsoft Word also substitutes some characters automatically but it’s not system-wide.
Ahhu so that’s why people in French language chat channels are always skipping literally all the diacritic marks and stuff. Thank you.
On Macs they’ve always been option-‘ and option-shift-‘.
There were people in D who wanted to use «», but indeed it was shot down for being painful for Americans to type.
But I’m glad it did get shot down becase really, why would we use that? Is it just because C++ uses <> and «» looks kinda like it without the same technical problems…? Or is there some deeper meaning beyond that? Ditto with ⟨ ⟩.
What I like so much about D’s decision to reuse plain () (with ! to indicate the presence of compile-time arguments) is that it questions that fundamental assumption that it should look different… notice instead of “generics” or “templates” or whatever, I just call it “compile-time arguments” - thanks to the syntax being so similar it lets us rethink how special the concept itself really needs to be.
All these problems (can) go away if we’re not married to storage of programs as a disconnected pile of 1-dimensional strings.
⦃ ⦄ is really pretty
There is also the Haskell syntax ! Just using one space. Adapted to a C++ like language that would look like
vector int
Haskell uses
()
for tupling, so it’s not that they get way without any kind of brackets …The title of the article is : “Language designers, stop using <> for generics “
But even with Polymorpic types, I’d still rather go with Type Application, rather than a tuple (,)
data Measure a b = Measure { quantity :: a , unit :: b }
value :: MyFancyType Int InternationalSystem -- here notice the type application
value = Measure 5 SquareMeter
And although I don’t use them, with the common language extensions
TypeOperators
orGenerics
, you would have the available type operator&
(likeInt & Char
) or:*:
(likeInt :*: Char
)This is a nice workaround – sadly it’s not used as much as it should be. :-/
Not sure the parenthesis have anything to do with tupling in this context, but more with precedence and/or associativity.
Technically, you could write complicated generic type signatures without any parentheses by abusing type synonyms:
would be equivalent to
no ? (I’m not sure, I’m just beginning my haskell journey :)).
Yeah, maybe tupling is the wrong word for.
As mentioned above, I think it’s a nice workaround, though it may become a bit annoying if it has to be done for every type involved.
Why not do it like Haskell and not use any brackets at all?
Yes and they use [] for lists and {} for records, that’s not the point. Type application just uses spaces, with () used to indicate precedence if necessary:
Maybe (Maybe a)
. I think this is by far the easiest to read.cf my other reply to you about Type Application
The D language uses plain
()
. In the definition, you add a compile-time parameter list before the run-time parameter list, and they work very similarly.void foo(T)(T t);
has a type T as a compile time param, then a runtime value param t of type T.To call it, well, many cases are implicit:
foo(0);
orfoo("")
bot just work, or you can explicitly call them by using the!
to indicate the presence of a compile-time parameter list:foo!(int)(5)
. (the parens around a compile time arg list can be omitted if the argument is a single, simple item, so you frequently seefoo!int(5)
in D code too.)It avoids all the
<>
ugliness and I really like the syntax similarity between CT and RT params; it is no longer a wholly separate concept and I think that semi-merging of compile time and run time code and concepts is one of D’s major strengths.as for their final thing about
[]
lol i like the array stuff but indeed it could just as well use()
for that same stuff too, so eh i kinda get their point i just like D’s wayI don’t think D’s approach is good. It feels like the basically understood the problem, but their solution is pretty much as ugly as the problem it tried to solve…
Well, that’s extremely subjective so yeah. When the proposed the drop parens thing I at first was aghast and voted against it since I thought it was hideous at the time. But I came around pretty quickly and like it quite a lot now.
I could probably get used to a
foo[int](5)
too though.I personally like typing the types like that :)
At the bottom, you suggest that collection literals are an abuse of square brackets. At first, that really surprised me (since I use those all the time). Now that I’ve thought about it a bit more, I can see that there’s some logic in not having them, since they could make a language less future-proof. For example, Python has a lot of literals for different data types. Outside of those types, anything you make yourself is going to have uglier syntax. Scheme has a
#()
notation for vectors (which I think is implemented using reader macros). Is the configurable reader macro approach the most future-proof, or does it encourage Balkanizing in libraries? What languages today don’t have some kind of array literal?I agree with the body of your article wholeheartedly, but I’m still unsure of the ending.
I think I rather like the so-called turbofish ;)
You may or may not already know, but Nim is an example of a language that uses
[]
for generics — and in fact for (overloadable) array access and construction as well. One place where this led to ambiguity and thus required some extra convoluted irregularity (unfortunately) is in the “method call syntax” for procedures. As explained in the manual, calling a generic procedure with method syntax must thus be written with an extra colon (:
), for example:object.method[:T](args)
Yeah, some languages sadly still try to use
[]
at the term-level – with predictable results. :-/I found this argument compelling enough to decide to switch the syntax of the toy language I sporadically work on from angle brackets to square brackets.
Cool! Thanks for the feedback! :-)
How did you deal with the last item mentioned on the page?
By not having gotten far enough along in my implementation of the language to have done anything interesting with array literals or indexing :)
Scala uses the
()
-based method call syntax for indexing into an array-like data structure, which I’ve always found a little bit unusual, but it certainly works. And even in languages that do have traditional[]
indexing syntax, it’s pretty common to call a method like.get
to (perhaps more safely) index into the data structure, so making that mandatory isn’t too outlandish. As far as creating literal arrays and array-like sequences, Rust offers a compelling example.vec![a, b, c]
is the standard way to create a vector literal - but this is a macro that desugars to something likeIn fact, this macro treats your exact choice of delimiter as unimportant -
vec!{a, b, c}
orvec!(a, b c)
work just as well. So having some sort of text tag likevec!
in the syntax for literal construction seems unobtrusive enough, and that exact tag can vary to indicate what sort of literal is desired (maybearr[...]
for arrays,vec[..]
for vectors,deq[...]
for a deque?).Of course this is a toy language that only I use, so it’s very easy to make arbitrary design changes based on blog posts I happen to have read :)
But how is this better than just doing
Arr(...)
,Vec(...)
,Deq(...)
?If those are macros that can take any number of arguments, it’s not, just slightly-different syntax. If those are functions, then your language’s type system needs to deal with functions that can take arbitrarily-many arguments.
If course methods should be able to accept varargs. What year is this? :-)
If your language needs to bust out macros to emulate varargs, you’re in trouble.
I agree and will do the same. I wasn’t at the stage of implementing generics yet. Yet I did do some research and looked at what other languages were using as syntax for generics. This article solidified me on that future syntax of [].
Somehow I am reading this as: somebody please make a programming font where <,> have the height of (,),[,],{,}. But maybe that will look too terrible for comparison.
Like hwayne said, there’s already the ⟨ ⟩ symbols, which are actually pretty nice. We don’t have a key for them, but you could remap the < > keys or set up a replacement in your editor for certain filetypes.
I think that if someone designed a language the relied on characters outside ASCII the reaction would be incredulous outrage from existing developers. It doesn’t matter how easy you make it to remap keys, the ideal of
code == ASCII
is so deeply embedded as to be a dogma.(preemtive rebuttal: APL doesn’t count, there are already 2 or three successors that are ASCII-only).
It could just be an alternative to the <> for people who disliked them. Many languages already support UTF-8 identifiers (Go and Raku, off the top of my head).
Thanks for clarifying. I agree.
I’m not familiar enough with Raku to know if the UTF characters are required for the language, or a just nicer-looking alternatives to the “standard” ASCII variants.
Ah, the famous if you look closely, those aren’t angle brackets, they’re characters from the Canadian Aboriginal Syllabics block!
Julia supports unicode identifiers and operators. It’s neat. It also uses
{}
for generics, so they avoided this problem entirely.This is really annoying to me because there are a lot of languages that have an ascii replacement for ∀, like
forall
andalways
and\A
when it would be so much nicer to be able to just drop ∀ in there and have the parser know what I mean.Haskell
-XUnicodeSyntax
is great :)That flag makes using the Unicode versions optional rather than mandatory, which is nice. ♥️
It looks nice if they’re very steep, i.e. very wide angle, almost like in some BIOS VGA fonts.
Spending a lot of time in functional languages has made articles like this really odd for me, like they are from another planet. I can’t imagine something like Haskell without lists prevalent everywhere and the square-bracket syntax. On the other hand Haskell has the approximate equivalent of generics without needing to use any bracket syntax at all.
Are there any nice, distinct pairs of brackets outside of the ASCII subset of Unicode that could be put to use?
Btw, There are many arguments here, but none have noticed that python has already made the choice to use square brackets.
PEP 484 gives the reasoning for that.
Ok, but why not use
{}
for generics? It doesn’t have the problem that[]
does, that is already known uses of it.I’m not sure that would be unambiguous with other usages of
{}
.Also, typographically
{
and}
looks way more busy than[
and]
– which is why you basically never see{
/}
without spaces around them.The only language I know that uses
{}
is Julia, and I’m not a fan for many reason.That’s where Golang devs were clever. No generics. Problem solved. 😉
There are other choices. I used « » as delimiters (not for generics) in a simple declaration syntax in 1991. Most languages support Unicode to some degree now, but I don’t think they take enough advantage of it. I just see people using Greek letters for math constants and emoji for variable names.
It’s common in julia to use unicode symbols as operators and elsewhere to make code look more like math. The base language defines a bunch of these for comparison, function composition, constants, etc.
Swift avoids the custom syntax trap with array literals since any type can implement subscript functions and ExpressibleByArrayLiteral protocol.
I’d consider these things covered by “abuse of [] for syntax conveniences”.
array(0) = 23.42 /* instead of */ array[0] = 23.42
feels like a very weak case to me.This is not how standard method calls work in most languages, so either this is just another form of syntactic sugar which feels confusing due to overloading with how real functions and methods work, or perhaps some tortured runtime implementation where the array function returns something that is simultaneously a getter and setter depending on how it’s used?
If this was the point they really wanted to make perhaps
array.set(0, 23.42) /* instead of */ array[0] = 23.42
would be better.[Comment from banned user removed]
Fair enough :)
You might be interested in a side project of mine, Forest. It’s a functional programming language that targets Wasm, but the notable thing for you might be that I am trying to make syntax an end-user concern.
The basic principle is that a syntax is a pair of reversable parsing and printing (as in, print -> parse -> print gets the same result for any module). I am aiming to provide tooling that runs a FUSE powered FS that projects the code into your preferred syntax for editing. This would enable developers working on the same project to use different syntaxes without needing any consensus (except around which syntax is in source control, but tooling means that’s not hugely important).
Part of the rationale for this was trying to separate the language’s core semantics from syntax, so that conversations like these can happen and change the syntax people use without having a huge kerfuffle over backwards compatibility.
Obviously this creates quite a few problems as well, but more and more I’m growing convinced that arguments like this over syntax would be much less important if we could just pick how we wanted our code to look.
Anyway, while Forest won’t have an authoritative syntax, it’s nice to think that if you used it in future you would hopefully just be able to use a syntax with square brackets for generics.
Looks interesting!
I’m approaching this from the other side, I guess: I believe that arguments over syntax should be less important by improving the syntax to something that has actively been designed and engineered.
This is in contrast to today’s standard design approach, which is basically creating a language by LARPing some 1960’s “design held together together with bubble gum, spit and baling wire” event, where the biggest inspiration is to speculate how C would look in a parallel universe in which BCPL was invented on a Thursday, and not a Tuesday.
Absolutely agree, I hope that having empowering users to play with syntax speeds up that process dramatically :)
Haha too good.
For Inko I went with
!()
, inspired by D. This means you’d writeArray!(Integer)
instead ofArray<Integer>
. It was a bit easier parsing wise since square brackets are already used for array/map indexing (e.g.some_list_of_numbers[10]
), making it difficult to figure out whatFoo[Bar]
is supposed to mean (what ifFoo
is an Array andBar
a constant containing the index?).!()
takes some getting used to, but I’ve grown to quite like it.The last section makes the point that
[]
for generics is desirable, because it precludes blessing random data structures with special syntax.Using square brackets for indexing doesn’t require blessing any data structure in particular. Using them for data structure creation does, of course, but that’s different.
The main point is that it’s still a bad idea to make a single function like “index” special and give it custom syntax, because there is nothing special about it.
I’m sympathetic to that idea too, but I think I might get frustrated with the verbosity of it if I were doing a lot of indexing.
What’s more verbose about
array(0)
thanarray[0]
?Oh, I like that style. I assumed you intended something like
get(array, 0)
.And what if you want to assign a vector within a tensor?
array((1, :, 10), vec)
I guess that’s reasonably clear.
Am I the only one who read it as “Language designers, stop using concatenation for generics”?