Hacker News new | past | comments | ask | show | jobs | submit login

The problem with that reasoning is that nearly every language with static typing also has some kind of type inference. You'll just wind up with a bunch of autos/vars.



Type inference is perfectly ok; it's still statically type checked and all the parameters and return-types are properly typed.

Type inference takes away the main argument against static typing: That static typing requires you type in a bunch of useless obvious types.


The problem with eating pizza is nearly every restaurant with pizza also has some kind of ice cream. You'll just wind up with a bunch of people eating ice cream and pizza all over the place.

Like, type inference is pretty widely regarded as a definite good. Additionally, technologies like row polymorphism make it possible to have statically typed and inferred duck typing. To me it's hard to hear this as anything other than mission accomplished. We made a static language that looks and feels like a dynamic one.

If you don't like type inference, you'll need to offer additional explanation as to why it is bad. Because otherwise you're statement only sounds like the one I made above. It sounds weird because one doesn't necessitate the other AND because they're both widely considered good.


Haskell has very powerful type inference but it also lets you ask the compiler "what type is this expression?" There even exists tooling that lets you automatically insert inferred type annotations. Sometimes the type it infers is more general than what you wanted, so you get the opportunity to fix it in a way that makes sense to you.


Similarly, the intellij plugin for Rust will reveal implicit type annotations.


Which isn't bad if the type inference is good.

Nearly all the type errors I see are things like

    var foo = "Hello World";

    bar = foo + 10;
That should scream it's head off.


To be fair, that's valid in Typescript because it simply uses Javascript's coercion rules.

You could imagine it having built-in rules for JS' coercions:

    string + number -> string
    number + string -> string
Maybe your snippet is provocative to some people, but in real code it would quickly fail once you actually use `bar` and were wrong about your assumptions.

At which point it's similar to languages with `Int + Float -> Float` and `Short + Long -> Long` in that perhaps you wish those operators weren't defined but it's at least consistent. And you'd find the error once you've passed those results to a function that expected Int or Short.


It's not the fall, but the stop that kills you. So in case of TS we don't know what was the intent.

Maybe it was this.

function suffixWith10(s: string) { return s + 10; }

But if the intent can be discovered by analysis of usages of the variable later in the code, then TS will scream loudly.


This is more of a knock against the compromises TypeScript's wacky type system has to make for JavaScript compatibility than anything else. It's not possible in a language that was really designed for strong static typing like Haskell or Rust.


The kind of errors I'm interested in preventing are more like this:

    var foo = 5;

    var bar = 100;

    baz = foo + bar;
Why is that bad? Well... what do 5 and 100 mean in that program? Did you just add age in years to pixels from edge? How do you know?

Appending an integer to a string is comparatively sensible next to adding age in years to pixels from edge.


Then you should not use a general numeric when you really should be using Pixel and Age types.


My broader point is that low-level representation doesn't matter nearly as much as semantics, and yet by default type systems are obsessed with low-level representations.

Appending a number to a string is a perfectly sensible thing to do when you're displaying information to a user, yet adding two numbers or concatenating two strings could be completely senseless and proof that the development process has gone off the rails.

My dream type system is strict about semantics and only handles representation in specific circumstances. The simplest way to talk about this is in terms of units of measure: Height is a type. Whether it's in inches or centimeters is a representation. Whether that representation is a number or a string is another representation. Keeping track of whether you're showing height-in-inches or height-in-centimeters to a user is useful, but burdening your mind with whether this height-in-inches is a string or a number right now when the runtime can just as easily convert between the two is senseless, and fretting over height-in-inches versus height-in-centimeters adding person-height to shoe-sole-height to get height-in-shoes is similarly senseless.


Your dream type system is out there, yet not manifest in any popular or somewhat-popular language.

I've yet to see a strictly/strongly typed language that makes this easy to do. Through some combination of plugins, esoteric languages, macros, or boilerplate, sure.

We're decades behind what is possible due to the practical.


Some languages don't care either way, and will let you add those types.


> Why is that bad? Well... what do 5 and 100 mean in that program? Did you just add age in years to pixels from edge? How do you know?

You know because you should be using sensible variable names that make it obvious.


Type inference for static languages are just syntactic sugar.

The IDE and the compiler will both scream if you try something like

var x = “John Smith”;

x = 5;


In C# var is always a place holder for the actual type which the compiler only accepts if it can figure out the type. If you do anything to the var that you can't do to the actual type it will still give you a compile time error.


Languages with type inference still require you to give enough information for a type to actually be inferred. Type inference isn't an escape valve for the things named in the GP comment, it's just a shorthand.


That's still good. In that case the language even writes the documentation for you!


I don't really use auto unless I'm interfacing with some templatized nightmare body of code where the typename is very difficult for my tiny human brain to interpret. Using "auto" is usually a code smell, because it means your type system is too complex for you to reason about.


This is a valid, strongly typed C# object:

var foo = {bar = 1, baz = “Hello”, boo = “World”};

You get auto complete help and compile time type checking.

foo.bar = “Goodbye”;

Won’t compile. What would it buy you to not use ‘var’ and create a one time use class/struct?


I use auto almost always. Makes the language "feel" more dynamic but you still have type safety.


It would make me ask why you need a one time use struct at all and probably remove it.


More complicated example:

var seniorMales = from c in customer where c.age > 65 && c.Sex == “male” select new {c.FirstName, c.Lastname, c.Age}

foreach(var customer in seniorMales) { Console.WriteLine(customer.Firstname + “ “ + customer.Lastname); }

Why would I create a class/struct for that use case?

Side note: this is why I find ORMs in most languages besides C# useless. Here, “customers” can represent an in memory List, a Sql table or a MongoDB collection and the LINQ expression will be translated appropriately to the underlying query syntax.

The “ORM” is integrated into the language and yes anyone can write a LINQ provider.


This us one of the cases where I would want you to write an explicit type for seniorMales. Reasoning about the LINQ expression is just complex enough that by not constraining its type to your expectations you can easily obscure a mistake in your thinking that would otherwise show up in the data types.


How so? You couldn't pass the anonymous type to another method or return it so the locality would be strong and the anonymous type is just a strongly typed POCO. Creating a class doesn't add anything semantically.

How do you feel about deconstructuring that is available in most languages?


I think the anonymous type is much better. It's type safe, you can easily see how it's defined but you don't pollute the rest of your code with one-off classes that make only sense within the function.

If I had to review the code and saw an explicit type I would recommend an anonymous type.


It is not better. I don't know what type FirstName or Age is. Can Age be negative? Is FirstName an array of char or a string? Can FirstName be more than 10 characters long? It could be a double value for all I can tell from that line of code!


The select statement is projecting the Customer type from either an external data source or an in memory source. That information wouldn’t be encoded into the POCO whether or not it was anonymous.

Changing select new {...} to Select new SeniorPeople {...} wouldn’t give you any more information. At most if I was writing that as part of a repository class I would be sending you back an IEnumerable<SeniorPeople>.

If you were to send me an expression to use in the where clause you would send me an

<Expression<Func<SeniorPeople,bool>>

You have no idea what that expression is going to be translated to until runtime.

Either way, you are just working with non concrete Expression Trees that could be transformed into IL, SQL, a MongoQuery etc. All of the constrainsts would be handled by your data store.

You don’t even know before hand whether you are iterating over an in memory list, or streaming from an external data source.

I could switch out Sql Server for Mongo and you as the client wouldn’t be any the wiser.

And we haven’t even gotten to the complication if the result set was the result of a LINQ grouping operation.


You are comstructing an elaborate strawman that does nothing to work in your favor. If anything, yur essay just makes it clear that it is impossible to glean the data type from the LINQ expression itself.

Yet, any code using its result will contain implicit assumptions about that data. Therefore, in order to maintain type safety, the expected type of the return value must he stated.

That type must also be independent from whatever LINQ does internally to produce that return value. Otherwise, the LINQ providers wouldn't be as exchangeable as you claim.


You also included information about “constraints” like is it a positive or negative number and string length. That wouldn’t be encoded in the type. Why would I go look up the types either way? I’m using a strongly typed language, the IDE would tell me that anyway. While I am writing code, I see a red dot showing me immediately if I’m using the type incorrectly. Any assumptions that were incorrect, I would get immediate feedback.


Not necessarily. Is Age unsigned or not? This constrains the possible range of values. In other contexts, there is a lot more information inferred by the integer type used.


Do you actually work with C#, LINQ and generics?


Yes. I am quite familiar with them.


Then tell me how you can model in C# a type where a property can’t be negative or a certain length.


    class Foo {
      public uint Bar { get; set; }

      private char[] _baz;

      public char[] Baz { get { return _baz;} }

      public Foo() { _baz = new char[5]; }
    }
This is cheating a bit on the array part. But the length of that char array is set in stone and cannot be changed from the outside. However, the Bar property uses a standard C# datatype.


I honestly thing that you haven't done any real work with LINQ or you are imagining problems that don't exist. The char array is not usable as string or we have to go back to 0 terminated strings like C.


I am not imagining problems that don't exist in the application domains I work on.

If your environment is so relaxed that aspects like signedness of an integer don't matter, so be it. But then you are also far removed from a domain where software correctness is non-negotiable.


It’s not aspects “like signednes” that’s the only constraint that you could embed in your defined POCO and still use as a select projection that would survive a LINQ expression that was turned into an expression tree. Your data store would enforce the other constraints. Your C style character array as a string wouldn’t work with either LINQ nor any of the standard library.

Are you really trying to go back to the bad old days with Microsoft C/MFC/WIN32/COM where you had over a dozen ways to represent a string that you had to convert to and from depending on which API you were using?


And your class wouldn’t work as part of the LINQ example. EF for example wouldn’t know how to translate it.


How do you want to model this in C#? Write a full class with all kinds of checking for a single LINQ operation? That will be a very bloated codebase.


Not necessarily. The types may just have very long names. In C++ for iterating through STL container auto is a godsend. The types are very straightforward and easy to reason about but just long.

for (auto it = s.begin(); it != s.end(); it++) {

is much easier to write than

for (vector<int>::iterator it = s.begin(); it!=s.end(); it++) {


I try to avoid using STL because it A) throws exceptions, B) has really convoluted type names, and C) dynamically allocates memory. These are all things you avoid in the embedded space.


Makes sense for embedded.


Any use of templates has the same problem, including templates that are used to generate highly efficient static constants and non-branching-at-runtime code.


Example?

Templates (template metaprogramming) help to avoid exceptions. You can check invariants first and jump into efficient code.


or almost like python:

for (auto &element: s){

even better with structured bindings:

for (auto [key, value]: map){


why we can't have nice things, in one comment.

auto is not a code smell on the contrary, it is used when your type system is easy enough to reason about that you don't need to write the actual type.


If it's so easy to reason about, why can't you just state the type of the variable? Doing so helps me understand what's popping out from the rvalue in assignment so that I can follow what your code is doing.

If you're worried about spending time changing type names during a refactor, look at it instead as an opportunity to evaluate the correctness of the code in the context of your replacement type. Use of the auto keyword avoids doing that and as a result enables you to create new and exciting bugs in your code.


> If it's so easy to reason about, why can't you just state the type of the variable?

because : - easy to reason about and easy to type are two differents things - naming everything increases mental load

> Use of the auto keyword avoids doing that and as a result enables you to create new and exciting bugs in your code.

to the contrary, porting a lot of my code to use almost-always-auto did actually remove bugs in the form of silent type conversions happening - e.g. std::pair<std::string, int> instead of std::pair<const std::string, int> (yay memory allocations), bad fp conversions...




Consider applying for YC's Summer 2025 batch! Applications are open till May 13

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: