I started off my professional career in dynamic languages. Nowadays I tend to prefer static types. I agree that there is a lot of noise and overhead in most places where static types are used. I found a good balance with typed functional programming, utilizing concise type definitions and inference (ML-family languages tend to have these). When used with pure functions, this kind of typing model feels like you have refactoring super powers. Yes, the compiler complains and shows squiggles on the screen. But they are more like a to-do list of things to fix, which is pretty helpful. Even still, rustling types can be mildly annoying at times, but I believe the payoff is worth it.
I think too, preference/personality comes into play. Some people might like to experiment first, and dynamic languages offer more flexibility there. But for me, I work through the ideas first in my head or on paper. Then often write the types and try to make bad states impossible before writing functions for those types. Just different strategies to the same goal.
I think too, preference/personality comes into play.
I think too, preference/personality comes into play.
I'm guessing that the application domain also comes into play. Maybe the more "algorithmic" a given problem is, the more useful these constructs become.
I mostly deal with business software. It has rules, but I would not characterize it as algorithmic. The primary benefit I see in how I do things is the refactor-ability of the code. This leads to easier maintenance for the life of the product, which leads to being able to keeping quality high and add high impact features at lower risk. The only domain I would be hesitant to use typed FP idiomatically would be one where low level performance is paramount. But I would almost certainly use types even still (and more procedural code), to control the memory structure and usage very carefully.
When I program in python, I just do searches across files to make changes and then run automated tests to confirm that the refactoring hasn't broken anything. I've used products like jetbrains before, and the IDE support for various things can be a nice touch for sure. I don't know that it really makes a massive difference to me though. I feel like maybe one kind of gets used to whatever one is doing.
Looking at signatures of things in languages like Java, C#, Typescript, can be unpleasant, especially when there are all kinds of expressions related to generics with nested angle brackets and so on. That's my biggest pet peeve with explicit typing. A secondary issue for me is that without duck typing, sometimes it can be a pain to add functionality because one has to adjust the typing accordingly. I do rather like duck typing where things just work if the call can be made. That said, type inference can in some cases make things look like duck typing - for every call with a given specific type, a concrete implementation would be compiled into the executable.
My only experience doing FP "all the time" so far has been with Haskell. I do like a lot of things in Haskell, but I don't know what it would be like to build real software in it. I've only written toy programs to get a basic sense of how to do things. I'm not sure if the type declarations also would tend to look as messy as they do in the languages that use the angle bracket notation for generics. As you mentioned, performance may or may not be a problem. A lot of business software involves doing data transformations on fairly complicated structures. I wonder how well a language like Haskell could handle that sort of thing.
I don't use a lot of IDE tooling as far as refactoring code or inserting snippets. As I do refactoring (as I'm typing in F#), the compiler tells me other things I have broken. It's kindof a todo list of places to touch. But I do the fixes myself to ensure it makes sense. If it gets to be too much pain to do this, then rather than reaching for better tooling, I take it as a signal that my design should be re-examined or organized better.
I agree about standard typed OO languages. There ends up being a lot of effort spent on just the type system... perhaps more than is saved when doing proper SOLID principles. So many interfaces and classes. Note that type annotations are optional in Typescript... it's basically just JS with optional extras.
Haskell is an interesting one among the ML family. I would consider Haskell "extremely typed" when used in a way that is idiomatic for it. I tend to gravitate toward more moderate languages like F#, OCaml, or Elm. My specific experiences are with F# and Elm. The typing overhead seems heavier in Elm, mainly because of the MVU pattern it uses. But Elm taught me a very valuable lesson about the benefits of pure functions. Over time, we have literally gutted our Elm code base, removing hundreds of lines of code overall, and drastically reducing the possibility of state-based bugs. Since all Elm code is pure (no side effects), refactoring has little risk... because once it compiles, it won't crash. (Refactors still take effort though.) I confess that we don't even write tests for our Elm UI -- it just hasn't seemed necessary. We do sometimes discover bugs, but so far blocking bugs are found, fixed, tested, and deployed within an hour. We have had regressions before, but usually it is because we didn't "make bad states impossible"... taking some extra time to adjust the types can literally prevent the need for that test. F# is an impure language, so you have to practice some discipline to keep functions side-effect-free. But it carries the same benefits when you do. Pure functions are especially nice to use in domain logic... makes testing really easy too. We extensively test domain logic, verifying both success and failure scenarios behave as expected.
For perf, I was thinking more of code which is close to the metal, like device drivers or file systems. Perf for business purposes is great in any of them, and they have knobs you can turn for specific cases. For example, if you look at this challenge, I wrote entirely procedural F#. And the author's Haskell version used some impure libraries to get about the same perf as mine (maybe better if both were run on same hardware). Both of these performed better than a naive C implementation.
We're a place where coders share, stay up-to-date and grow their careers.
We strive for transparency and don't collect excess data.