17 ms·
Language Design: Use 'ident: Type' not 'Type ident'
- TazeTSchnitzel 7y agoA more practical reason as a language designer is that C and C++-style type-before-name syntax is a nightmare to lex and parse, as you can't tell whether A * B; is a multiplication or a variable declaration, or whether A<B, C> D; is a two comparisons with a comma operator or a templated variable declaration, without first knowing the names of all declared types. This means in practice that you have to declare types before they are used in a file, which means forward-declarations if they are defined later, that you can't separate lexing and parsing because the parser has to provide constant feedback to the lexer, and that misspelling a type name can lead to a syntax error! C++, not content with merely inheriting C's problems, throws in the “most vexing parse” as a bonus.
- Animats 7y agoIt's a historical problem in C. Originally, C had only built-in types. Structs were declared with struct foo { int x; int y; }; which is easy to parse. Then came typedef. With user-defined types, foo*bar; isn't parseable with a LALR(1) parser until you've seen the definition of "foo". Even the ordinary case foo bar; needs more than one lookahead to parse. This is a headache for compilers, and a huge headache for anything that wants to work on single source files without seeing the included files. It's a big win if files are parseable without their dependencies. The Pascal/Modula/Ada family all are. I think Go is. Not sure about Rust, but it probably is. C and C++, no. (The hacks for template syntax in C++ are painful to think about.)
- TazeTSchnitzel 7y agoAh, C not originally having typedefs explains why the syntax wasn't thrown out as too annoying to begin with. Syntax creep I guess. An easy solution to the problem would be to do what e.g. Haskell does where case-sensitivity determines a type name. For better or worse, C didn't do that.
- lisper 7y agoIn Dylan all types were delimited with <angle_brackets>. Personally I always found that very aesthetically pleasing.
- Riverheart 7y agoSame in Powershell except they use square brackets [string] $foo = "foo"
- zelphirkalt 7y agoIn my brain the instantly translates to "list of strings". Not sure square brackets are such a good choice.
- lmkg 7y ago> Not sure about Rust Rust has made a point of being parser-friendly, to support tooling. The entire reason the turbofish ::<> exists is to avoid ambiguous grammar. (This operator seems to vex people, I don't get the hate.)
- estebank 7y agoDo note that Rust the language is regular and easy to parse, but rustc the compiler actually performs bounded lookahead and contextual parsing on parse errors to detect common typos and invalid syntax that would otherwise be vexing to users. The nightly only type ascription : syntax is a good example because it crops up in several different contexts as a 1-substitution typo and used to be terrible (may it burn in a fire). The turbofish is another case where we now suggest the correct syntax, but there are many, many more.
- usrusr 7y agoSurely that's an example of how parser-friendly syntax empowers tooling? I imagine that it would be much harder to guess intention and fixes from lookahead and context if those were already necessary to make sense of correct code?
- WalterBright 7y agoBeing parser-friendly is not at all the same thing as being user-friendly. The difficulty of writing parsers is way overblown. One thing to avoid is depending on the existence of the symbol table to parse correctly. (C++ has this problem.) Needing a symbol table makes it hard for parsers like source code formatting and syntax highlighting. Another is to avoid raw string literals that reach back and try to unwind the results of previous "phases of translation".
- barrkel 7y agoAgree with a caveat; if the syntax has many ambiguities, with resolution solved by a clever parser, it'll be confusing for users, especially newbies, because they'll tend to get errors unrelated to what they were trying to achive. Redundancy in syntax can be a bonus.
- barrkel 7y agoYeah, the easiest way to do it is return a different token type from the lexer for type names, which means the lexer looks up the symbol table when it sees identifiers. Amusingly, the Delphi dialect of Pascal has an ambiguity in its grammar from how it made function pointers work: function g: Integer; // ... f(g); Depending on the definition of f, this could be passing the function f by reference (as a function pointer), or passing the result of calling f. Delphi looks at the argument type to resolve the ambiguity, but it stays awkward in overload scenarios, when there's a choice between Integer or function pointer arguments. At some point around about 2009, I added the ability to specify explicitly that you want the call, to eliminate the ambiguity: f(g());
- yaktubi 7y agoOn the other hand doesn’t forward declaration make sense? The code is declaring it depends on the forward declaration defined “somewhere else”. In a sense it is documenting the the classes/structs better.
- BruceEel 7y agoYears ago, I stumbled upon a Visual C++ 6.0 bug (if memory serves me right), just by playing around trying to understand C/C++ decls. It would crash on a stray: *c; at the beginning of a translation unit. Didn't K&R have a tiny, tiny C declarations parser (printing out 'human readable' equivalents) example in their book? I think the caveat was that it needs to assume it's dealing with a declaration...
- stormbrew 7y agoIt's possible to argue here that the problem is not so much the order of type-then-name but the overloaded meanings of * and < at the grammar level.
- lilott8 7y agoMore generally this falls into a discussion of context-specific vs context-free grammars. Of which C++ falls into the former, Java falls into the latter.
- MaxBarraclough 7y agoThe grammar of C++ is not a context-sensitive grammar. It's Turing-complete, on account of its template metaprogramming capabilities. I'd be very surprised if Java's grammar were context-free. Do you have a source for this? I wasn't able to find one with a quick search.
- battery_cowboy 7y agoIt's Turing complete because it could simulate a Turing machine, not because of metaprogramming. The language brainfuck is Turing complete, for example.
- saagarjha 7y agoChecking if a Brainfuck program is well formed (i.e. can be run) is a linear time operation. In C++ this can take forever. They have different complexities.
- battery_cowboy 7y agoThe original comment was about Turing completeness, and it was defined incorrectly. I was giving an example of a dead-simple language that was Turing complete, because the claim was that metaprogramming made C++ Turing complete.
- Groxx 7y ago`int * A` and `A * int` both seem ambiguous unless a) you require "int-pointer" naming instead of "pointer-int", and/or b) you have a separator (like the title uses) so this becomes `A * B` vs `A: * B` which is indeed unambiguous but in a very different way. i.e. without extra rules you can't tell either way, so it comes down to the extra rules. `A * B` is unambiguous for type-before-name if you require "pointer-int" since it would need to be `* A B` for A to be a type. (quite possibly there are counter-examples when you get into weirder corners, but my point is that it's not as simple as presented)
- WalterBright 7y agoD resolves the: A * B; // declaration or multiplication? in an unusual way. If it was a multiplication, the result would be thrown away and be pointless. Hence, it is a declaration. But what if A overloads the * operator and the side effects are desired rather than the result? D has a philosophy that arithmetic overloads should be for arithmetic-like operations, not I/O, template metaprogramming, or other nonsense. Hence if you try it like that, too bad, so sad, it'll be treated as a declaration.
- Ididntdothis 7y agoI wouldn’t mind if such a statement in isolation would be a compiler error and if languages in general were much stricter. So far all languages I have seen that are very forgiving in terms of syntax (PHP comes to mind) seem to breed sloppy programmers and software written in these often have stupid little bugs caused by typos.
- WalterBright 7y agoThis particular issue has not caused any "silly typo disease" problems that I'm aware of in D. In fact, pretty much nobody notices it, it just works the way people expect it to. (There are some other things in D that are designed to discourage trying to overload arithmetic operators for non-arithmetic purposes. For example, < <= > >= cannot be overloaded individually, only as a group.)
- downerending 7y agoArguably a programming language is ultimately a user interface, and the more intuitive the interface, the better. In 2020, not sure we should care that much about how hard compilers have to work to achieve this. Computers and software are here to support us--we're not here to support them.
- MaxBarraclough 7y ago> In 2020, not sure we should care that much about how hard compilers have to work to achieve this. That's not right. Long compile times are a real issue for some programming languages even today (Rust and C++). > Computers and software are here to support us--we're not here to support them. A false dichotomy, and not a perspective that offers any insight. Sometimes a low-level language is appropriate, and sometimes it is not.
- Gibbon1 7y agoC++ is stuck in this trap where because it's slow to compile, the compiler maintainers increase the amount of optimizations the compiler does to make it faster. Which of course makes the compiler even slower. Which motivates them to increase the amount of optimizations. Which makes the compiler yet slower. I feel part of the problem with rust is it doesn't have quick and dirty mode thats fast and a production ready mode that is slow but does all the checks. I do this with my C programs, using various formal analysis tools which are slow to vet the code before releasing it.
- steveklabnik 7y agoThe checks are not the most expensive part of compiling Rust.
- Gibbon1 7y agoWhat is it then?
- 7y ago
- MaxBarraclough 7y agoI missed your mention of the 'most vexing parse' before writing this up, but for those not in the know, here's another fun quirk of C++ syntax: Declare a local named neko of type Kitten, passing a value to its constructor: Kitten neko(42); Declare a local named neko of type Kitten, without passing a value to its constructor: Kitten neko; Declare a function named neko with zero parameters and with return type Kitten: Kitten neko(); https://en.wikipedia.org/wiki/Most_vexing_parse https://en.wikipedia.org/wiki/Most_vexing_parse , https://stackoverflow.com/a/620149/ https://stackoverflow.com/a/620149/
- a1369209993 7y agoAnd Ceiling Cat help you if you try to declare something like: Kitten neko(Felis<0xCA7>::catus); where catus may be either a typename or a variable depending which template instantiation you landed on. There's probably some way to say: Kitten neko(Felis<is_function_type<typeof neko>::value_of>::catus);
- MaxBarraclough 7y agoBeautifully terrible!
- mariodiana 7y agoI was just thinking about pointer syntax today, and if you ask me, there are a lot of problems that could be avoided if language designers took a page out of Guido van Rossum's book and extend his idea of forced spacing. Take the first example you've given, and let's just talk about variable declaration: int* a; int * a; int *a; That's all the same. That's wrong. Obviously, you can write a lexer and parser that doesn't give a crap, but the human mind does. If you think it doesn't, that's only because you've internalized the various cases. It should be this: int* a; The type we're talking about is a pointer to a variable of type int: in other words, "a" is an int-pointer. If you were to create a macro, you'd do something like this: #define int_ptr int* Do you see what I'm getting at? The star in this case is a suffix, equivalent (in our minds) to "_ptr". Conceptually, it doesn't belong anywhere else than attached to the type. It's a compound type, conceptually. Now, take the star being used in a different context: int b = 10; int* a = &b; printf("%d\n", *a); There, though we see the same character, it's a completely different thing. It's a dereference operator. Conceptually, it belongs attached to the pointer variable it is dereferencing; and since the star is already used in one context as a suffix, here it should be used as a prefix. This is no good: printf("%d\n", * a); It doesn't matter that "this compiles." That's not what this is about. Many C programmers (and programmers in other languages, even Python) are used to writing things like this: int c = x*y; That's wrong. Sure, the lexer and parser don't care. But that makes the language worse, for the human operator. "But it saves space!" Spare me. The thing with this one example, using the star, is that what we have is the equivalent of a homonym. We have one sign that is actually three different words. Mandating spacing removes the ambiguity you're complaining about. C is what it is, but if we imagine someone were going to write it today, they should incorporate the above and mandate spacing. For the sake of the humans. "ident type" is not the only solution.
- klyrs 7y ago> It should be this: > int* a; I must disagree... the following sends the wrong message the reader: int* a, b; Though I also agree that dereferencing is best without a space. Perhaps the correct suggestion is to not declare pointers and instances on the same line, but that's a convenience that many seem to enjoy
- threatofrain 7y agoMakes me wonder why Dart went the same way. One thing I don’t like about type first is that what you want is often more specific than the type.
- WalterBright 7y agoIronically the article chose the < > syntax: class Id<T>() { Instead of the D syntax: class Id(T) { Not using < > for template parameters eliminates all kinds of parsing problems.
- ZoomZoomZoom 7y agoI don't see it being ironic. In Rust () is always a tuple, so if you see it that way it makes sense () isn't used for type declaration.
- pierrebai 7y agoThis has nothing to do with order and everything to do with the chosen grammar of C and C++. You can have an ambiguous grammar in a language that puts the type after. A * B could be ambiguous even if A is the identifier name and B the type. All arguments in the "Language Design" notes are of the same nature.
- raverbashing 7y agoI think this C pointer ambiguity would be "easy" to solve with one of those minor changes: 1 - (sorry, formatting is messing this up) mandate the declaration: 'A*' or '*B' without space (depends if you think the pointer is a type of variable or a type of type) 2 - add some keyword like "type" Though the compilers are able to deal with it, so it's not a big issue
- thom 7y agoYou'd have to show me some data that one is easier to work with than the other, because fundamentally types _are_ names, and I find them just as expressive as variable names (many of which are just named after types, lets be honest). Even if I didn't, I don't think my brain struggles to read things in either order (or indeed in languages where types are rarely mentioned).
- Ar-Curunir 7y agoI mean most programming languages put some sort of punctuation between names. Eg: function calls are punctuated by parens, imports are punctuated by `::` or `.`. The same should happen for types.
- thom 7y agoI think the vast majority of code that exists in the world today has at most one space between a variable and its type.
- Ar-Curunir 7y agoThere’s a difference between “things that are implemented in popular programming languages” and “things that are implemented in most programming languages”
- thom 7y agoYes but you’re clearly making some sort of argument by authority and I think that works both ways. I just don’t see any reason to hold firm beliefs either way on such a trivial issue unless someone has a citation that I’m missing that proves one form increases comprehension or productivity, or reduces bugs.
- adamnemecek 7y agoIt's easier to parse if nothing else.
- palerdot 7y agoLanguage Design Notes on Rust [0] from the same blog looks interesting too ... [0] - https://soc.me/languages/notes-on-rust.html https://soc.me/languages/notes-on-rust.html
- echelon 7y agoThis is an interesting list. Some of the things have been addressed (`extern crate`). Many of the issues I disagree with: `Buf` is strictly better than `Buffer` (less typing, like `fn`). I have no issue with mixing `CamelCase::snake_methods`, and actually find it to be quite beautiful. The good parts of being Pythonic. I would like to see the alternatives to turbofish. What exactly is the author suggesting? And what's wrong with `println!` and `format!` ? It isn't articulated. `[]` misuse is bad, semicolons aren't consistent, `PathBuf` is inconsistently named, etc. Agree. `io::Result`, ... Maybe there will be some cleanup in a future language edition.
- millimeterman 7y agoLanguage Design: This stuff doesn't matter that much. Focus on more important things. Syntax isn't unimportant, but don't waste energy on trivial matters like these. Just pick something and people will get used to it. Focus on the semantics of your language - that's what really matters.
- bsder 7y agoActually, syntax IS important because a language has to be able to be read by both a human AND a computer, nowadays. One of the things that using ":" does is it makes what is "type" and what is "name" unambiguous to both human and computer. This is, famously, one of the failings of C/C++. Determining what is a type and what is a name is excruciatingly difficult.
- millimeterman 7y agoSure, but there's no arguing to be done there. Is the grammar context free? Ideally, can it be parsed with small constant lookahead? Yes? Cool, no further discussion needed. I'd be more open to this kind of discussion if there was an ounce of actual research behind what makes syntax more/less readable. As it is, it's just a bunch of people arguing endlessly about their very specific preferences. Just pick something sensible and move on.
- Someone 7y agoLanguage design affects how good autocomplete and error messages can be. That is hugely important. Having said that, this article doesn’t advocate “ident: Type”, it advocates ”marker ident: Type”. That marker is essential for ease of parsing and thus for autocomplete (it won’t try to autocomplete the ‘ident’ part by looking at variables in scope or function names, for example) and error messages (it could signal when name shadowing occurs, for example)
- mamcx 7y ago>Syntax isn't unimportant #104#101#108#108#111,[Space]world![Space][Space][Tab][Space][Space][Tab][Space][Space][Space][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Space][Space][Tab][Space][Tab][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Space][Tab][Tab][Space][Space][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Space][Tab][Tab][Space][Space][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Space][Tab][Tab][Tab][Tab][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Space][Tab][Tab][Space][Space][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Space][Space][Space][Space][Space][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Tab][Space][Tab][Tab][Tab][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Space][Tab][Tab][Tab][Tab][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Tab][Space][Space][Tab][Space][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Space][Tab][Tab][Space][Space][LF] [Tab][LF][Space][Space] [Space][Space][Space][Tab][Tab][Space][Space][Tab][Space][Space][LF] [Tab][LF][Space][Space] [LF][LF][LF] yep, no important at all.
- kevmo314 7y agoThis feels a little nitpicky/idealistic, I don't think the post does a good job of conveying why it's more beneficial. > This means that the vertical offset of names stays consistent, regardless of whether a type annotation is present (and how long it is) or not. Why is this necessarily desirable? Strong typing systems have very expressive types, to the point where if something is typed correctly, most of the time my property names are just an alternative casing of the type. Types can be just as expressive or even more expressive than variable names. > The i: Int syntax naturally leads to a method syntax where the inputs (parameters) are defined before the output (result type), which in turn leads to more consistency with lambda syntax (whose inputs are also defined before its output). Maybe this is nice in theory? But `Int` really isn't an output here, and the value being assigned isn't either. Rather this seems more like `f(i, Int, value) -> assignment`. It seems just as arguable that `f(Int, i, value) -> assignment` is appropriate. It seems like some of these are rooted in a "pure mathematical" approach which I can surely appreciate, but ultimately lambda calculus is as much a language as any other programming language, saying "lambda syntax does it this way" doesn't convince me very much.
- echelon 7y agoI've been using Rust a lot recently, which puts names before types and inputs before outputs, and I will absolutely attest to how much mental work is saved by ordering things this way. Skimming or reading Rust comes twice as easy as reading Java, and I do a lot of both. Sure it's an anecdotal report, but I have a real sense here that I feel compelled to report. As other posters have stated, this order makes parsing easier. But I also suggest this benefit extends to your own brain's parsing ability as well. The old order is indirect and suboptimal and makes you think harder.
- kevmo314 7y agoTypescript also orders its parameters this way. Between all the Rust and Typescript vs Java and C++ code I've written, I really haven't found that either is better than other, it just seems like a largely arbitrary choice. Making parsing easier for the compiler is a convincing benefit, would have been nice to see that mentioned in the article. I think that's a substantially stronger reason to prefer types after names. I'm not sure if I parse either faster or slower though.
- TOGoS 7y ago> The ident: Type syntax let’s developers focus on the name by placing it ahead of its type annotation. I agree with the sentiment, but that apostrophe is bugging me.
- nitrobeast 7y agoIt is presented here that name before type is easier to read as a matter of fact. I’m not so sure. In math or languages where type info is optional, we often write “x = 5”. When type info is required, it is natural to evolve to “int x = 5”. Readers would naturally focus on the latter part. When we write “x: int = 5”, the type info is in the middle. We cannot skip it even when we just want to focus on the name and value.
- andrewla 7y agoMany languages allow you to elide the type, which is another nice thing about the type following the identifier. In Scala, in particular, types are not the assigned type like in C (where they also serve as the storage specification) -- they are assertions, that the compiler will check are compatible with the code. So `val x: int = "hello"` is no good and the compiler can cut it short right there; this is especially useful as call-site documentation.
- throwanem 7y agoConversely, a lot of languages will infer type from first assignment, so in e.g. TypeScript "let x = 5", x is inferentially typed as 'number' and the type checker will throw if the implicit constraint is later violated. This reduces the need for explicit type annotations, clearing up a lot of the visual and cognitive noise.
- dastbe 7y agoThere is still a distinction between primitives and object types in Scala, so it's not correct to say that types in Scala aren't used for storage specification. It's also the case that there are plenty of languages where all types imply storage specification and they support plenty of type elision.
- skybrian 7y agoIf you have local type inference, an alternative would be "x = 5: int".
- 7y ago
- malwarebytess 7y agoSeems a solution in search of a problem. Worse I think it creates visual clutter.
- Pfhreak 7y agoInterestingly, I find ident: Type significantly more difficult to read. Having the type information helps me contextualize what I'm about to read -- it narrows the mental search space I need to explore when parsing the name. For example, knowing something is a float, double, int, or string can make an ident named "releaseTime" mean different things. I also find that whitespace is more consistent when using Type ident, you get rivers where the spaces all line up, so all the type declarations AND ident declarations align. Whereas with ident: Type, I find it much more difficult because of the variable length of identifiers. (Yes, one could fix this by using tabs, but if idents vary in length by more than one tab stop, it becomes difficult to read horizontally.)
- andrewla 7y agoAnother very nice thing about this is that it is much much easier to parse, because only one kind of thing can go in each position of the phrase. Simplicity in parsing is something that I think is underrated in language design; the harder it is for a computer to parse, the harder it is for a human to parse, and parsing code is 90% of the programmers work (the other parts being 9% debugging and 1% authoring new code).
- Pfhreak 7y agoI replied elsewhere that I found the opposite to be true. So I suspect that different people will find different styles to be easier/harder to parse. > the harder it is for a computer to parse, the harder it is for a human to parse, I don't think this is true -- assembly (or bytecode) is very easy for the computer to parse, but much, much harder for humans to parse. English is much easier for humans to parse, but pretty difficult for computers to parse.
- tlbsofware 7y agoI’m surprised this didn’t touch on the IDE autocomplete suggesting variable names. In Java you would have something like `LocationBuilder locationBuilder` which makes users just tab complete the variable name to quickly have access to a variable. The argument in this article was about names being prioritized and I think forcing no auto completion on a variable name would force the developer to be slightly more descriptive than the variablized string of a class name
- dilap 7y agoMaybe a little orthogonal -- I could easily imagine IDEs still doing something like transforming the input ": FooType" into "fooType: FooType" w/ the name selected and ready to be tabbed past.
- mr_tristan 7y agoIn Kotlin, IntelliJ has no problem with this. As you type a new value: `val id`, and you have `IdentName` defined in scope, the value `identName` is suggested automatically. Not all IDEs are the same, though, and I'm not sure how sophisticated this feature was to implement.
- hocuspocus 7y agoJetBrains' autocomplete does pretty well either way, you can be similarly lazy in Scala or Kotlin.
- MayorMonty 7y agoThe first point is the most appealing to my brain at least. Type inference is a really useful feature (when paired with a nice IDE) and having a single standardized prefix to declare variables regardless of what type it is can help the mental model. This is especially true with more complex non-obvious types, where you may not know exactly what type you have without the hint from your environment
- qppo 7y agoI disagree. The syntax design should flow from the design of the language itself and whether or not you use prefix or postfix notation for type annotations depends heavily on what makes sense within the semantics of the type system. Design a language before you design a syntax.
- throwanem 7y agoGranted that the pathological case of the error you warn against is Perl, and that should be enough of a cautionary tale for anyone. But a language is a user interface for programmers, too. Some affordance is merited, especially in a case like this where prefix vs. postfix may affect ease of parsing, but seems most unlikely to influence how the type system actually behaves.
- qppo 7y agoYou're right, I just don't care for the author's notes on language design because they're all on syntax design, which is an impossible task to do in morsels without knowing anything about the rest of the language or how it is supposed to work. I do prefer postfix because I think it flows very nicely "this is-a thing assigned-to that" is nicer than "thing called this assigned-to that." In terms of the impact on the language, optional postfix annotation makes it a bit trickier if you want to make the identifier optional, and in languages that support it you tend to see special syntax to deal with that case (which breaks the author's fetish for self consistency). Personally I think ordering of the trio of "alias" "thing" "value" should be consistent across the language, which extends far past variable assignment, and any one of the trio can be left out.
- disconcision 7y agoWhat are examples of language semantics which are better served by pre/postfix type annotations? Also, what exactly do you mean by making the identifier optional?
- Izkata 7y agoStrong disagreement here. "type ident" flows with the data during assignment, doesn't confuse the infix operators, and doesn't misuse ":" from a human-language standpoint. For example: val x: String = "hello" The type interrupts the flow of data from "hello" to x, so one thing that pops into mind is that this is typecasting the value to a string before storing it. Nope. Another possibility I instinctively see this as is doing a comparison and assigning the result (either true or false in this case) to x. Nope. And human-language wise, colon is "description: explanation" (or more generally: general to specific), which actually fits this syntax better: val String: x = "hello" ...and at that point, just remove the extraneous stuff: String x = "hello"
- lliamander 7y agoI agree, though I could see the merit for a standalone declaration: val x: String x = "hello" The type at this point is almost like a comment. For declaration and assignment though, I agree that reading "ident: Type" is harder for me. Perhaps an interesting idea would be to have the type at the end of the expression. Like so: val x = "hello": String Essentially, you're making a type assertion on an expression. Since it's an assignment expression (the value of which would be the assigned variable) then it also type checks the variable.
- djur 7y agoMost statically typed languages don't even need the type assertion in a case like this, though. A literal has a definite type (hopefully), so the type of x can be inferred. val x = "hello" Standalone declarations are the most important problem to solve here.
- lliamander 7y agoYeah, type inference is preferred (and pretty common). I'm just saying that, if I had (or wanted to) set the type, at the end of the expression would be my preferred place.
- hinkley 7y ago
- IshKebab 7y agoYeah I'm pretty sure the real reason for this is that it is way easier to parse types if they are after the name. Especially complex ones like functions.
- pansa2 7y ago> This means that the vertical offset of names stays consistent This is also an argument for using keywords of the same length for introducing a variable and a constant. If that’s desirable, it rules out the obvious choices `var` and `const`. Possibilities include `var` and `val`, which may be too similar-looking, and `var` and `let` - but are people used to (from JavaScript) `let` being mutable? Any other options?
- Someone 7y ago“Let” is mutable in Basic, too, but the part of the population that is used to that is shrinking. As to short, equal length options for ‘let’ and ‘val’: one could consider using punctuation. Forth uses colons instead of ‘fun’, and I think, in a concise language, one could get used to using, say, ‘!’ for immutable and ‘~’ for mutable. Unfortunately, they aren’t easy to type. An alternative could be to always assume immutability and only use ~ in the rare cases where one needs to mutate. So, a simple foo = 3 or, if one wants to simplify parsing: = foo 3 introduces a new immutable variable, and ~ foo = 3 or ~ foo 3 a mutable one. If we allow leaving out spaces: ~foo 3 that starts to look like using sigils to indicate mutable state. I think that might be a good option in a mostly immutable language. I think I would use Forth’s colon instead of ‘=‘. That would make ‘=‘ available for equality testing, allowing us to get rid of ‘==‘.
- jyounker 7y agoI think the author misses the single biggest advantage of `identifier: Type`. The moment `Type identifier` syntax encounters higher order functions and types, you end up with messes of parenthesis. Figuring out what a type means then involves bouncing back and forth across the type definition. With `identifier: Type` complex higher order types still parse linearly left to right. It's enough of a UI issue that people will end up avoiding higher order functions in `Type identifier` languages simply because they're a mess to express.
- Too 7y agoYup, especially with structural typing as in Typescript when you don't have aliases to your all your type constraints, having identifier:{complex:mess, of:{nested:stuff}} is easier than other way around.
- dirtydroog 7y ago> 1. Names are more important than types I can't really agree with this at all. With type aliasing the new typename can render the variable name pretty much redundant.
- SamReidHughes 7y agoEven better: Use ‘ident Type’.
- melolife 7y agoThe most important result of this design is that the syntax unambiguously determines whether you are referencing the type or value axis, and enables you to split them accordingly. Having worked with Scala and been forced to return to a C-style language, this is probably one of Scala's most overlooked features.
- Ono-Sendai 7y agoOne downside of the 'ident: Type' approach is the extra colon character. The major downside of the 'Type ident' approach, is that if 'Type' is optional, then the parser can't be sure if its parsing the 'Type' or the 'ident' when encountering the first token. In practice this isn't too hard to solve however, it can be handled with some backtracking. In my language, Winter, I have chosen the 'Type ident', approach, mostly due to similarity with C, C++ and Java. I do sometimes wonder if I made the right choice however. Maybe it could be an option? :)
- geofft 7y ago> The ident: Type syntax let’s developers focus on the name by placing it ahead of its type annotation. If this were true, we'd have to conclude that speakers of name-then-honorific languages like Japanese ("Graham-san") are better at remembering and focusing on people's names than speakers of honorific-then-name languages like English ("Mr. Graham.") But there's no evidence of that, is there?
- js8 7y agoOne additional reason why it is beneficial is that you can then naturally extend typing to any expression, not just identifier. This can help the type inference (and also can serve as documentation), which is (IMHO) a must in a modern programming language.
- strictnein 7y agoSorry, but this article starts off with an excellent example of why this is horrible: val x: String = "hello" String x = "hello" The first line reads: "value X is of type String and contains hello" The second line reads: "String x contains hello" val and : are fluff and add nothing. Arguments about it being tougher to parse would have some merit if this wasn't all figured out almost 50 years ago.
- scriptproof 7y agoIf consistency if so important, why do we have: function, func, fun, fn, def, etc... depending the author? For clarity, use "function", for simplicity use "fn", other forms are just fancy.
- tharax 7y agoIf consistency if so important, why do we have: function, func, fun, fn, def, etc... depending the author? For clarity, use "function", for simplicity use "func", other forms are just fancy. "This is the standard we should all adopt!" https://xkcd.com/927/ https://xkcd.com/927/
- gherkinnn 7y agoRob Pyke talks about this in more depth here in the Go blog [0] 0 - https://blog.golang.org/declaration-syntax https://blog.golang.org/declaration-syntax
- marmada 7y agoA benefit of ident: Type is that it allows you to express complex anonymous types. Example from Typescript: const foo: 'A' | 'B' | 'C' = 'A' Which states that foo must belong to the given union type. How would this look in a Type ident language? const 'A' | 'B' | 'C' foo That doesn't seem right. There's no clear barrier between the type and the identifier name. Here's another contrived example: const foo: () => Promise<void> = async (x) => console.log(x) Here foo is of type "() => Promise<void>". How would this look in a Type ident language? const () => Promise<void> foo = async (x) => console.log(x) To me, this is unclear because it is hard to tell where the type ends and the actual function begins. Last example. const foo: { [string]: number} = {"hello": 3} I believe says that foo is an object with string keys and number values. What does this look like in a Type ident language? const { [string]: number} foo = {"hello": 3} I think all of the Type ident examples are more confusing because it's to tell where the type ends and the name begins (this is most clear in the first example). This probably makes syntax highlighting worse/parsing more complicated/is tougher on the user. With ident: Type, it is very clear that the type starts after the ":" and ends before the "=" sign.