Why make it complicated?
-
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.
-
let a = String::from(“Hello, world!”).into()
I’ll see myself out.
At least be fair and cut out the
.into()
-
At least be fair and cut out the
.into()
And bow to the compiler’s whims? I think not!
This shouldn’t compile, because .into needs the type from the left side and let needs the type from the right side.
-
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.Until now, I looked at
let
and thought, "maybe they just felt like doing it that way".
Makes a lot more sense now. -
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.
Maybe that's what the people developing spoken languages thought, while normalising conversations overdependent on context.
But hey, now we have another comedic tool in anime.
-
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.wrote on last edited by [email protected]How do you do nested parameterized types without it becoming ambiguous though? That's IMO the biggest advantage of the bracket syntax. For example:
Map<Tuple<Int, Int, Int> Int>
-
So I think it's still probably unclear to people why "mix of keywords and identifiers" is bad: it means any new keyword could break backwards compatibility because someone could have already named a type the same thing as that new keyword.
This syntax puts type identifiers in the very prominent position of "generic fresh statement after semicolon or newline"
..though I've spent like 10 minutes thinking about this and now it's again not making sense to me. Isn't the very common plain "already_existing_variable = 5" also causing the same problem? We'd have to go back to cobol style "SET foo = 5" for everything to actually make it not an issue
wrote on last edited by [email protected]any new keyword could break backwards compatibility
Wouldn't that happen anyway with variable and function names? Any type other than primitive/built in ones are usually camel case so lower case keywords are more likely to clash with single word variable and function names, unless you restrict the cases of those too or allow keyword overriding or something.
-
How do you do nested parameterized types without it becoming ambiguous though? That's IMO the biggest advantage of the bracket syntax. For example:
Map<Tuple<Int, Int, Int> Int>
Map (Int, Int) Int
. Kind of a bad example because tuples have special-case infix syntax, the general case would beMap Int (Either Int Bool)
. Follows the same exact syntax as function application just that types (by enforced convention) start upper case. Modulo technical wibbles to ensure that type inference is possible you can consider type constructors to be functions from types to types....function application syntax is a story in itself in Haskell because
foo a b c
gets desugared to(((foo a) b) c)
: There's only one-argument functions. If you want to have more arguments, accept an argument and return a function that accepts yet another argument. Then hide all that under syntactic sugar so that it looks innocent. And, of course, optimise it away when compiling. Thus you can write stuff likemap (+5) xs
in Haskell while other languages need the equivalent ofmap (\x -> x + 5) xs
(imagine the\
is a lambda symbol). -
Map (Int, Int) Int
. Kind of a bad example because tuples have special-case infix syntax, the general case would beMap Int (Either Int Bool)
. Follows the same exact syntax as function application just that types (by enforced convention) start upper case. Modulo technical wibbles to ensure that type inference is possible you can consider type constructors to be functions from types to types....function application syntax is a story in itself in Haskell because
foo a b c
gets desugared to(((foo a) b) c)
: There's only one-argument functions. If you want to have more arguments, accept an argument and return a function that accepts yet another argument. Then hide all that under syntactic sugar so that it looks innocent. And, of course, optimise it away when compiling. Thus you can write stuff likemap (+5) xs
in Haskell while other languages need the equivalent ofmap (\x -> x + 5) xs
(imagine the\
is a lambda symbol).wrote on last edited by [email protected]Interesting. Thanks!
-
any new keyword could break backwards compatibility
Wouldn't that happen anyway with variable and function names? Any type other than primitive/built in ones are usually camel case so lower case keywords are more likely to clash with single word variable and function names, unless you restrict the cases of those too or allow keyword overriding or something.
wrote on last edited by [email protected]Yeah, it's in my edit I realized the same thing. I'm thinking it doesn't actually really make sense and the real reason is more "the specific way C does it causes a lot of problems so we're not poking syntax like that with a 10 foot pole" + "it makes writing the parser easier" + maybe a bit of "it makes grepping easier"
-
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.
wrote on last edited by [email protected]Yeah, I mean Rust is only verbose if you want it to be.
let foo = "bar";
is valid rust too, no need to declare the type and definitely no need to declare the lifetime.For that matter, if you ever declare something as explicitly
'static
in code that isn't embedded or super optimized, you're probably doing it wrong. -
I would because I know TypeScript and I don't know Rust.
ok but then you can't do Rust, so this does not apply.
but if you did.. !