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

Yeah my semi-hot take is that type annotations in functions is a feature not a bug. It forces legible interfaces (with the exception of nasty generics I suppose).



But they can just be auto-generated in documentation. It gets pretty annoying to type annotate everything for nothing but warm fuzzies when the compiler can figure it all out for you.

Also, any decent IDE will show the types any way.

The complaint about the types is just strange and shows a failure in getting Rust out of their head. Once you get used to F# and OCaml, then going back to a language that forces type annotations everywhere gets old and seems archaic because you spend all this time telling the compiler what it already knows (in a language like F# and OCaml). However, there are times in which type annotations are needed, especially in F# when dealing with objects. I generally type annotate when I feel the name of the argument doesn't capture the type.

Writing F# often feels very Pythonic or Scheme-y, but at the end of the day, everything is being statically typechecked, so it's the best of both worlds.


> The complaint about the types is just strange and shows a failure in getting Rust out of their head.

I second this a lot. It's very strange to me that the author is complaining about powerful type inference. In my limited experience with ReScript, I've found the type inference to drastically reduce boilerplate while providing the same type safety guarantees as TypeScript. Moreover, your editor or IDE can always tell you what the inferred type is. I am really not sure what I am missing. The author headlines the paragraph with "Where are the types? [sic]" but the types are there! They're inferred and visible in your IDE!

The provided code snippet doesn't really help illustrate the point either, IMO. It will look unfamiliar to those who've never used OCaml before, and to those who have done more than a fizzbuzz in OCaml, it will look okay.


Well there's also the fact that the types flow downward from the interface. Like the source of truth comes from your function parameter types that are always explicitly written. One thing that tripped me up writing OCaml is that you could have a mistake in your function that'd show up as a bug in a completely different location due to the inference algorithm. Like dumb example but say I have `foo` instead of `foo()` that changes the function return type to be a () -> int instead of int. You'll get the error when you use the function result instead of inside the function.


> It gets pretty annoying to type annotate everything for nothing but warm fuzzies when the compiler can figure it all out for you.

This is sometimes fine for internal code, but for external interfaces you want a human to determine the interface contract you’re agreeing to, rather than the minimal set or the maximal set of types the function supports as currently-written.


Perhaps, but presumably code that is meant to have an external interface will be in a module and will have an *.mli file to it, and that is an excellent place to constrain your types explicitly if you must.


In OCaml most people are writing signatures to which their modules conform (usually in separate mli files). The problem is that checking the signature happens after all the type inference so you can’t use it to disambiguate constructors for function arguments/results, and they can’t be used much to guide users who make type errors in their functions. I think it was a good idea that rust put the types in arguments to non-anonymous function.


> because you spend all this time telling the compiler what it already knows

But a program should be optimized not for reading by a compiler but for reading by a human. This is essential for maintainability.

The question is not how hard it is for a compiler to deduce types, but how hard it is for a human (and a human that hasn't necessary written this ode, and hasn't necessary read and remembered the whole codebase) to figure it out.

It is not a simple question in general. Too much "infrastructure" clutter can hide the logic and intent of the code too. So to get the right balance in a language or in the code is non-trivial.


> But a program should be optimized not for reading by a compiler but for reading by a human.

I absolutely agree, more than you could imagine, but I think this is not a case where that's the issue. Type annotating everything will quickly make the code harder to read. There's a reason why people like Python. But the problem with Python is that you don't have a way to figure out the types. With F#, just hover over anything and get the type.

> This is essential for maintainability.

Even more to the point, over type annotating will make maintenance harder, not easier. This is because you are fixing the types statically. When you then go to update code in the future, you have thrown out the benefits of type inference and now have to go around manually updating all of these type annotations.

So you took the quote a bit out of context. If you're type annotating code, you're telling the compiler something it already knows, you're telling the human reader what it can already find out immediately for any value, you're increasing the surface area of needed updates, and making the code harder to read.


Unfortunately in the 21st century still too many people like to program the hard way without tools that improve their workflows.

Maybe they should do batch compilations as well, why bother with interactive programming.


OCaml has a separate language for module interfaces where types are required. Even better, it allows you to abstract over types and make them entirely opaque, so that users of an interface never have to look at any of the implementation details.


As you get more comfortable with functional programming, the type signatures tend to get more complex for sophisticated code. Those "nasty generics" become reliable friends.

For example, since functions are first-class objects, they are often passed around as parameters to other "higher-order" functions. Explicitly declaring the types of all these functions is tedious at best, and confusing at worst.


I'm not convinced that these type signatures have to be that bad. You could probably make them pretty tenable, especially with a good code formatter.

Also, I'm confused. We're using type driven development and writing the type signatures as part of the documentation, we should be thinking in terms of types, why would they be difficult to write?


They're not bad. They're just a pain to type out in detail every time. Here's an example signature from an F# library I like called FParsec:

    pipe2: Parser<'a,'u> -> Parser<'b,'u> -> ('a -> 'b -> 'c) -> Parser<'c,'u>
That's fine for documentation, but I'm glad it's not cluttering up the actual implementation, which starts like this:

    let pipe2 (p1: Parser<'a,'u>) (p2: Parser<'b,'u>) f =
        ...
You can see how the code does explicitly specify the types of `p1` and `p2`, but doesn't bother spelling out the type of `f` or the return type. Although we are thinking in types the whole time, this sort of flexibility in the implementation is important for real-world functional code.

(For anyone wondering, this function takes two parsers and a function as input. It sends the output of each parser to the function, and the result is itself a parser. This is equivalent to a well-known abstract function called `liftA2` in a language that supports typeclasses, like Haskell.)

[0]: https://www.quanttec.com/fparsec/reference/primitives.html#m...

[1]: https://github.com/stephan-tolksdorf/fparsec/blob/master/FPa...

[2]: https://hoogle.haskell.org/?hoogle=liftA2


This is a really good question. When you write generic code in a language with higher-order functions, type signatures can get pretty hairy with type variables and quantifiers flying around (forall a. a -> foo a). In a functional language with type inference, it's mostly managing those for you, and you can (usually) just write code in a natural way and it automatically is polymorphic, but in a safer way than dynamically-typed languages.


I think that is not a hot take at all, haha. For some reason, in TypeScript people like to have return types inferred, and while I do that just because I'm lazy in my own projects, we enforce explicit return types in our work codebases simply because you never know when something might change in the function body.




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

Search: