6 ms·
I go a little back and forth on this with my experience in F#, which relies heavily on inferred types. You can write a lot of F# before you need to add type ann
by algorithmsRcool 2y ago
I go a little back and forth on this with my experience in F#, which relies heavily on inferred types. You can write a lot of F# before you need to add type annotations, but eventually, things become a spiderweb. The key issue is when you make a 'small' change to some method/value, the changes ripple through the program creating confusing errors sometimes where the compiler is trying to knit things together.
After a while, I found myself adding back types in a decent number of places to "anchor" the type inference, indicating that a certain type/signature is fixed and a change should be carefully considered.
I still don't know how folks deal with these kinds of changes in weakly typed languages without always allowing bugs to pour into their code over time. But I do love the "move fast" and low boiler-plate aspects of "typeless" coding.
- scalaisneat 2y agoI agree with this sentiment but view it inherently as a feature of languages like f# and Haskell - That small ripple at compile times makes trusting refractors easier. After having experienced this, languages like python become really challenging to grok without heavy unit testing.
- kibwen 2y agoThere's also a middle ground which is to support function-local type inference, but not inter-function type inference, which guarantees that such an "anchor" is never all that far away (especially never in another file). This is the approach Rust uses.
- MoreMoore 2y agoI think what a lot of people miss is that this ripple effect always exists. Whether you have strong types or weak types, it's hard and often impossible to avoid. All strong types do is make it explicit at compile time (and with static analysis), instead of leaving it to be discovered in tests or at runtime. This is why I'll use Python for small one-off scripts but anything that will be in regular use I prefer to do in Java. Even though I have many more years of experience with Python.
- jonathanlydall 2y agoAs someone who works in C# and TypeScript, I suspect that people who work in weakly typed languages either: - Don’t have big projects. - Have lots of unit tests to cover some of what a C# or Java compiler would have caught through static analysis. - Don’t even consider doing certain kinds of refactoring (which would be trivial on a strongly typed language) because they have no way of knowing what will break.
- leptons 2y ago4th option, they are just better than you are at managing dynamism.
- jonathanlydall 2y agoEven ignoring for a moment that I consider being highly practiced at dynamism a largely pointless skill while strongly typed languages exist, sooner or later a code base which is always growing will reach a certain size where even the very best person in the world at dynamism will have to resort to unit tests to cover what a compiler can do for free, or just accept that certain refactoring or changes to the code base are unreasonably expensive to do with any reasonable level of confidence.
- leptons 2y agoI'm the guy with a huge javascript codebase, written before typescript existed, and I have none of the problems you describe. Huge refactors are also not a necessity in every codebase, if the code was written well to begin with. And so far using typescript in other projects has not produced the supposed benefits a lot of people say are inherent with typescript. There is no magic happening that saves me from writing bad code, because I wasn't writing bad code before typescript. Refactoring isn't all that difficult either, even without tests. But I guess YMMV.
- thfuran 2y ago
- kybernetikos 2y agoHaving worked on a large js codebase back before typescript or flow or the closure compiler existed, I found that the process was not very different to any code - you check your preconditions on entry to code that will be called from elsewhere, it's just that those preconditions in a dynamically typed language may include the types of your arguments. If you do that, then type errors typically cause your code to fail fast, often on first load, in easy to understand ways. Overhead is just a couple of extra easy to understand lines at the top of about half your functions (probably less boilerplate than go's error handling forces on you). Given the fantastic iteration speed that the Web platform had (and still does to some extent) I didn't really miss a compiler for catching errors. The main improvement is that IDEs find it easier to support big refactoring. I also worked on a mid sized scala codebase a little later on. Scala is probably better now than it was then, but despite (perhaps because of?) the cleverer type system, everything was so slow that it actually took longer for many bugs to be highlighted by the compiler than they would have been found by hitting f5 in a browser window with a good js codebase. That was when I realised that as a developer I care a lot about when an error is highlighted in wall clock time and not at all about which compiler phase it was discovered in.
- titanomachy 2y agoI think Scala has a good compromise here, where all function signatures require explicit types and (almost) everything else can be inferred. Maybe F# does the same? I find that this is enough "anchoring" to allow for sensible error messages most of the time, although occasionally I'll sprinkle in more annotations if the inferred types would be particularly confusing to readers (usually because they're too general).
- neonsunset 2y ago> You can write a lot of F# before you need to add type annotations, but eventually, things become a spiderweb. The key issue is when you make a 'small' change to some method/value, the changes ripple through the program creating confusing errors sometimes where the compiler is trying to knit things together. I found working with type inference being its own skill. If you know how to place type annotations well, the ripple effect only affects one or two callsites and then the type inference just continues to infer what you meant in the subsequent code. Though we may be approaching the structuring of the code differently so YMMV. But I haven't had issues even with complicated member constraints which replicate dependent typing.
- yawaramin 2y ago> the changes ripple through the program creating confusing errors sometimes where the compiler is trying to knit things together. F# is derived from OCaml, and they both support interface files–in the case of F#, with the .fsi extension–which is designed to solve exactly this problem. After settling on the surface area of your module, you nail it down by writing an interface file with the exact types you want to enforce. The compiler then uses this to check the usage of the module by its consumers, and provides much more accurate type errors. It works really well.
- rubenvanwyk 2y agoGlad F# was mentioned, my mind went their immediately as well. The ripple is definitely intentional, and from what I've found the dotnet compiler is quite performant so not so much experiencing the feedback lag that other mentioned when it comes to Scala's compile time. The thing I appreciate of F# is that if it compiles it probably "just works".