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.
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.
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.
Maybe they should do batch compilations as well, why bother with interactive programming.
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.
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.
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.
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?
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...