Why make it complicated?
-
More specifically, they're borrowing the more mathematical meaning of variables, where if you say x equals 5, you can't later say x is 6, and where a statement like "x = x + 1" is nonsense. Using "let" means you're setting the value once and that's what it's going to remain as long as it exists, while "var" variables can be changed later. Functional languages, which are usually made by very math-y people, will often protest the way programmers use operators by saying that
=
is strictly for equality and variable assignment is:=
instead of==
and=
in most C-style languages.Unless youβre in JS.
-
Made with KolourPaint and screenshots from Kate (with the GitHub theme).
wrote on last edited by [email protected]The actual reason why let ... in syntax tends to not use C-style "type var" like syntax is because it's derived from the syntax type theory uses, and type theorists know about parameterised types. Generics, in C++ parlance, excuse my Haskell:
let foo :: Map Int String = mempty
We have an empty map, and it maps integers to Strings. We call it foo. Compare:
Map Int String foo = mempty
If nothing else, that's just awkward to read and while it may be grammatically unambiguous (a token is a name if it sits directly in front of
=
) parser error messages are going to suck.Map<Int,String>
is also awkward but alas that's what we're stuck with in Rust because they reasoned that it would be cruel to put folks coming from C++ on angle bracket withdrawal. Also Rust has ML ancestry don't get me started on their type syntax. -
Then the second part of my statement applies.
In the case of Rust, you can also omit the type annotation in the vast majority of cases and the compiler will infer it.
-
Good, now invent a keyword for variables you don't want to declare the type. And now that you have a mix of keywords and identifiers on the same place, you can never update your language again.
Also, make the function declarations not use a keyword too, so you get the full C-style madness of code that changes meaning depending on what libraries you import.
Good, now invent a keyword for variables you don't want to declare the type.
auto
. Also in D, you only needconst
if you don't want to specify a type for a constant, the compiler automatically inferres it to you.Function declarations can be easily decyphered from context, no problem.
-
I think you can do
const thing = ... as const
to lock down the mutation?So is
let
in some languages. In Rust, you have to constantly opt out from immutability withlet mut
, which makes writing more procedural code feel like you're fighting with the compiler, and otherwise I don't really see the rationale behind full functional coding. I only had a bug caused only once by unwanted mutation, the hardest part fixing it was to learn the proper use of my debugger tool. -
I have never heard of this problem for C. Can you elaborate or point to some articles?
The basic problem is that identifiers can be either types or variables, and without a keyword letting you know what kind of statement you're dealing with, there's no way of knowing without a complete identifier table. For example, what does this mean:
foo * bar;
If foo is a type, that is a pointer declaration. But if it's a variable, that's a multiplication expression. Here's another simple one:
foo(bar);
Depending on how foo is defined, that could be a function call or a declaration of a variable bar of type foo, with some meaningless parentheses thrown in.
When you mix things together it gets even more crazy. Check this example from this article:
foo(*bar)();
Is bar a pointer to a function returning foo, or is foo a function that takes a bar and returns a function pointer?
let
andfn
keywords solve a lot of these ambiguity problems because they let the parser know what kind of statement it's looking at, so it can know whether identifiers in certain positions refer to types or variables. That makes parsing easier to write and helps give nicer error messages. -
Javascript gonna Javascript
JavaScript*
-
It's also valid rust syntax.
But if it were rust, this meme would not make sense, since you would just type
let a
and type inference would do its thing. Which is much more ergonomic.Type inference is a pretty big thing in TypeScript as well though. In fact it's probably the biggest thing about it, IMO.
-
I was thinking the same thing. who would write typescript if they could just do Rust?
I would because I know TypeScript and I don't know Rust.
-
Made with KolourPaint and screenshots from Kate (with the GitHub theme).
IT'S SHOWTIME I NEED YOUR CLOTHES YOUR BOOTS AND YOUR MOTORCYCLE a GET TO THE CHOPPER a HERE IS MY INVITATION "ArnoldC is the best." ENOUGH TALK TALK TO THE HAND a YOU HAVE BEEN TERMINATED
-
The actual reason why let ... in syntax tends to not use C-style "type var" like syntax is because it's derived from the syntax type theory uses, and type theorists know about parameterised types. Generics, in C++ parlance, excuse my Haskell:
let foo :: Map Int String = mempty
We have an empty map, and it maps integers to Strings. We call it foo. Compare:
Map Int String foo = mempty
If nothing else, that's just awkward to read and while it may be grammatically unambiguous (a token is a name if it sits directly in front of
=
) parser error messages are going to suck.Map<Int,String>
is also awkward but alas that's what we're stuck with in Rust because they reasoned that it would be cruel to put folks coming from C++ on angle bracket withdrawal. Also Rust has ML ancestry don't get me started on their type syntax.There is also the thing where the compiler might mistake your c++ style variable declaration for a function, e.g.
String myfunction():
String myvariable();
-
Type inference is a pretty big thing in TypeScript as well though. In fact it's probably the biggest thing about it, IMO.
I don't know typescript. But if that's the case, this meme doesn't make much sense.
Who writes the types of variables in a language with type inference unless forced by the compiler?
-
Rust is verbose, but C++ might still take the cake with its standard library templates. Especially when using fully-qualified type names...
auto a = ::std::make_shared<::std::basic_string<char, ::std::char_traits<char>, MyAllocator<char>>>();
A reference-counted shared pointer to a string of unspecified character encoding and using a non-default memory allocator.
fully qualified type names make any language awful.
Here's the same example in rust:
let a = std::rc::Rc::new(std::vec::Vec<u8, MyAllocator>::new());
I believe u8 also comes from a module, so it would be something like
std::u8::u8
, but I'm not sure. -
The basic problem is that identifiers can be either types or variables, and without a keyword letting you know what kind of statement you're dealing with, there's no way of knowing without a complete identifier table. For example, what does this mean:
foo * bar;
If foo is a type, that is a pointer declaration. But if it's a variable, that's a multiplication expression. Here's another simple one:
foo(bar);
Depending on how foo is defined, that could be a function call or a declaration of a variable bar of type foo, with some meaningless parentheses thrown in.
When you mix things together it gets even more crazy. Check this example from this article:
foo(*bar)();
Is bar a pointer to a function returning foo, or is foo a function that takes a bar and returns a function pointer?
let
andfn
keywords solve a lot of these ambiguity problems because they let the parser know what kind of statement it's looking at, so it can know whether identifiers in certain positions refer to types or variables. That makes parsing easier to write and helps give nicer error messages.wrote on last edited by [email protected]I feel this is related, and hightlight this even further, look at all the ways to initialize something in C++.
https://www.youtube.com/watch?v=7DTlWPgX6zs
If you are really lazy, have a look at making an int at around 7:20. It's not horrible that alone, but it does show how many meanings each thing has with very little difference, added on top of years of legacy compatability accumulation. Then it further goes into detail about the auto use, and how parantheses, bracket, squiggly bracket all can be used and help with the mess.
-
Rust is verbose, but C++ might still take the cake with its standard library templates. Especially when using fully-qualified type names...
auto a = ::std::make_shared<::std::basic_string<char, ::std::char_traits<char>, MyAllocator<char>>>();
A reference-counted shared pointer to a string of unspecified character encoding and using a non-default memory allocator.
I don't understand why verbose is bad. Verbose is maintainable.
-
I don't understand why verbose is bad. Verbose is maintainable.
Not when taken to such an extreme so as to obfuscate the meaning and behavior of code, and make it difficult to understand how you would arrive at that code.
Sane defaults serve to reduce verbosity without obfuscating meaning, simpler syntax with different ordering and fewer tokens reduce verbosity to make the code easier to read by reducing the amount of text you have to pay attention to to understand what the result is.
I imagine there's also a distinction to be made between verbosity and redundancy - sometimes extra text might fail to carry information, or carry information that's already carried elsewhere. I'm not sure where the line should be drawn, because sometimes duplicate information can be helpful, and spacing out information with technically meaningless text has value for readability, but I feel like it's there.
-
JavaScript*
Blame my phone's autocorrect
-
Blame my phone's autocorrect
Weird, my phone gave me the correct capitalization when I just used the swipe typing.
-
I don't know typescript. But if that's the case, this meme doesn't make much sense.
Who writes the types of variables in a language with type inference unless forced by the compiler?
Maybe it's a language without type interference?
Either way, it sometimes makes sense in TypeScript to help the type system out a little bit.
let array: string[] = [];
In this situation, the type system can't infer that the empty array should be a
string
array, because there are no items to go by. -
Not when taken to such an extreme so as to obfuscate the meaning and behavior of code, and make it difficult to understand how you would arrive at that code.
Sane defaults serve to reduce verbosity without obfuscating meaning, simpler syntax with different ordering and fewer tokens reduce verbosity to make the code easier to read by reducing the amount of text you have to pay attention to to understand what the result is.
I imagine there's also a distinction to be made between verbosity and redundancy - sometimes extra text might fail to carry information, or carry information that's already carried elsewhere. I'm not sure where the line should be drawn, because sometimes duplicate information can be helpful, and spacing out information with technically meaningless text has value for readability, but I feel like it's there.
wrote on last edited by [email protected]Frankly, I have way more often ended up scratching my head for some cryptic piece of syntax than the opposite. Sure I could sit down and spend the rest of my life learning each language designer's favourite syntax optimisations, but I kinda don't want to. I'm a human, not a parser.
And frankly, a good IDE editor should be able to fold/unfold verbose syntax. Better for it then to be there and folded, than for it to not be there and for someone to have to work backwards the intendrd meaning of every single line of code.