I wonder whether the perception that type safety slows down Ruby (or ES6) development comes from the fact that the type systems are bolted on after the fact.
I wonder whether the perception that type safety slows down Ruby (or ES6) development comes from the fact that the type systems are bolted on after the fact.
Being correct from day one will cause too much unnecessary friction. You (usually) don't know the entire program architecture until you make a full prototype, and even if you have a plan, there will always be some part where some unexpected consequences force you to rearchitect some parts. And since you don't know the entire program architecture you architect your program bottom-up, and most of the dynamically-typed languages allow an interactive development environment at a flexibility that typed languages can't provide.
Think about developing Python inside a Jupyter notebook, or Common Lisp inside a REPL. You turn on a REPL, open up a file, write a function, send the function to the REPL, test the function you just wrote, and when I find a mistake I can just redefine any function I would like to change. This process allows fixing mistakes on-the-fly. Typed languages don't allow this (even ones that have a so-called REPL) at this flexibility, (since they emphasize on being correct all the time), and cause too much unnecessary friction while prototyping.
Thus a need for a dynamically-typed language that can enforce types after prototyping.
I ran into this recently when I was working with Rust. And don't get me wrong; I love Rust. But I was experimenting with a new way of doing things in my codebase and was adding a new kind of statistical analysis, and I wanted to run a test case to see what the result was... However, I couldn't because the compiler's typechecking refused to even compile my code, when I knew for a fact that the type errors had nothing to do with the code path of my test case.
I get that that's the whole point of static checking. I can change a large codebase and know exactly when/where I've broken something even if I'm not familiar with all the code, and it will barf at me. But the fact that I can outsmart it, even some of the time, leads me to believe that there will always be a place for dynamic languages.
If 99% of my code has type errors, but there is one code path that is true, who is the compiler to say that that isn't the one code path that I want to take? When experimenting, I build my code dozens of times. The vast majority of the time, I am the only one who consumes the build result. Once in a rare while, I will reach a steady state and build a release. It's really only then that I want the compiler to barf at me and refuse a broken build.
I must not be the only person on the planet who wants to do this sort of thing.
But for many simpler cases (e.g. the majority of idiomatic java), it can be as simple as substituting an offending imperative line with a throw RuntimeException statement that parrots the compiler error message. I'm sue that they are doing a lot more than that.
I'd been a C/C++ programmer for about 10 years! I guess I just forgot that compilers can catch typos if you let them.
I know exactly the feeling, but I don't think it's just the types that give it but that it's compiled. I similarly moved on from ruby after 5 years but to Elixir and was similarly surprised to (re-) learn that a simple compile step catches so many braindead typos, misnamed variables, not imported function calls, and the like.
Types go above and beyond this, but I think there's a huge step up just going from interpreted to compiled.
The feedback loop is within your editor, that’s a step before the app console!
Before that, there was the Rhino and Nashorn ones, which although JavaScript based, did the job.
These days Kotlin is also pretty good. Perhaps not quite as fast to prototype but there’s little need for converting afterwards.
I wouldn’t necessarily recommend it, but I have written 500-line Python programs without executing/testing any functions along the way, that “just worked” the first time I ran them with no serious bugs. And not because I am especially clever or have an amazing memory or unusual attention to detail, but just because the code is easy to make clear and straightforward.
(But this was just with basic data types and a few standard library modules; dealing with other people’s poorly documented / confusing APIs makes this gets a lot harder.)
I inherited legacy Python projects at work and I'm shocked by the time I'm wasting fighting with the lack of types. Most of the time I have no idea (and my IDE neither) what methods and properties are available on variables and function parameters. And what's the most insane to me: I'm sitting next to people with LOT of experience in Python and I see them losing as much time as me when maintaining and debugging some basic piece of code they have written not even 2 weeks ago.
If I am really this lost with a piece of code in Python or Ruby, I usually start an integration test with an added line to call the debugger.
With introspection you can then easily see what methods a given object has.
> And what's the most insane to me: I'm sitting next to people with LOT of experience in Python and I see them losing as much time as me when maintaining and debugging some basic piece of code they have written not even 2 weeks ago.
That is insane. What kind of code coverage do you have and what kind of tests?
It is true that you have to write more tests in a dynamic language than in a static typed language but as tests often convey the information of what was intended to be done much better than code, I don't consider this a drawback.
But if the tests are lacking, you are at a much greater loss than with static typed languages.
With VS and C#, I can hit ctrl+space and get a list of absolutely everything that's in the contextually relevant public API. So much faster than introspection. If I'm using an object initializer to instantiate something, I can hit ctrl+space inside the object initializer to get a list of all the available public properties -- no typing the names required. No having to remember the order of arguments of methods, or even their types. Autocomplete means I can usually get away with somewhere between zero and three keypresses to get exactly what I want highlighted in the intellisense.
Forget efficiency via vim/emacs keybindings. Static typing + a strong intellisense/intellisense clone lets me code literally at the speed of my thoughts most of the time. I end up feeling like I'm stuck in molasses the few times I have to write some JS.
> It is true that you have to write more tests in a dynamic language than in a static typed language but as tests often convey the information of what was intended to be done much better than code, I don't consider this a drawback.
I understand your point, but for me, the extra tests I write in dynamic langs just end up making me feel like I've duplicated the work of the static checker in other languages, and in a non-reusable way.
For instance if features need to be demonstrated or prototyped on a test env. for some time before they are green lighted to be fully baked, loosely typed languages are ideal. You get the speed of prototyping, and all the heavy cost comes after the main parts have been validated.
It's less painful than having to do prototypes after prototypes in a more rigid language.
But of course this advantage disappears when working more in small waterfall iterations, where everything is basically set in stone from the start.
I'm sure those languages and their ilk have advantages for prototyping, but I agree, mandatory typing in other languages isn't a burden. If you already have to reason about what arguments are acceptable in functions, what objects can receive what messages and what those messages should contain, you already have typing — just inefficiently stored in short term memory.
Those who are the biggest proponents of these languages as useful for prototyping are also not the ones who rewrite their code in type-safe languages, so being able to add type annotations for the sake of their colleagues who eventually have to turn these prototypes into code upon which a team can collaborate can only be a useful thing.
I've thought about this for a while and I think the "fast prototyping" reputation is an accidental feature that sticks because of history.
Python and Ruby were just better languages than Java 1.4, C++, Perl and PHP were at the time. They were so much better for not only prototyping, but writing - it was easier to get something working and iterate in Python than it was in Java. And not only were they better, they were better at the right time when the internet exploded.
Now, it largely feels like languages like Java and newer languages like Go have caught up, but Python and friends still enjoy the feature of "fast prototyping." Now I think this is because since they had gotten so popular, they now have a massive ecosystem of libraries and idioms that makes it so easy build applications.
You still don't get this with Java or Go, but you certainly do with ML-like languages (Haskell, SML, OCaml, F# etc.)
A lot of Haskell developers will tell you that writing Haskell feels a lot like a functional Python, and they use Haskell for scripting as well as application development.
And while Java's type inference is not the same has having HM around, it is already q89te friendly.
1) readable (somewhat english-like syntax) 2) terse (short and to the point, no extra setup code needed) 3) meta-programming/DSLs
It still remains unbeatable for readability and terseness..
Which Ruby do you mean? 1.8, 1.9, or a more current one?
I've yet to see the equivalent of Django or Rails when it comes to rapidly assembling a database-backed CRUD app in a statically typed language.
I think it could be it's own product, but leadership doesn't care.
Duck typing is also a minor win for "do what I mean" generics - you never have to worry about the type specification being too narrow, just pass an object with the necessary methods and it'll work until it breaks.
1. When the types are complicated enough, people no longer read them. They skim over them and don't bother to ever "load" them into short term memory. Sometimes it makes sense to make a type alias for the subcomponents, but many times they're one-off types and it wouldn't make sense. As the person writing the code, figuring out what the types actually are and writing them down doesn't actually help anyone. It's just busy work that the compiler or interpreter could have inferred. For example, if I have a hash nested in an array, nested in a set, you can access them all with brackets chained together. I don't actually need to know the full type in order to use it. So why bother figuring out the exact type that it is? Which brings us to...
2. When the types are abstract, with type variables and type constraints, someone has to put in the work to come up with the most sensible type signature. Sometimes that is the most general type that the code satisfies (using a Rust trait, Haskell typeclass, or a Java interface), but sometimes it's not. It really depends on the context. Should I abstract over this type and introduce a type parameter to the function? Or should the type parameter be for the entire struct/class? These are oftentimes complicated questions, with no clear answer. Weighing your options and choosing is real work. If I now say that this function will accept different sized ints, I may no longer be able to just call i64::something(). I literally need to change how I write my code or even import a new helper library to do it. (I have had to do that in Rust. And I love Rust, BTW.)
OTOH, in a dynamic language, all of that work is just gone.
What I'm saying is that types for a compiler must be precise and general. But types that you keep in short term memory are oftentimes vague (something that I can index, or some kind of number) or specific to a test case. And it doesn't matter.
That would imply ill intent on my part, and there was nothing of the sort. All I said was that I couldn't see the sense in certain things and, without further explanation, I didn't. Other comments helpfully pointed out the context for why the assertion is so often made and I was happy with it.
On the other hand, I think I'd need to see some hard evidence for the following assertions:
> When the types are complicated enough, people no longer read them
What is a complicated type?
> As the person writing the code, figuring out what the types actually are and writing them down doesn't actually help anyone.
Citation needed. I find static typing tremendously helpful in my own code, especially if it's code I haven't worked on in a long while.
> It's just busy work that the compiler or interpreter could have inferred
… such that I only notice errors when the code is running, not beforehand. Without type annotations, interpreted languages like Python and Ruby are happy to let wrong types in function calls and message passes slide — which is why frameworks like Sorbet exist, to help eliminate that class of error.
> 2.
All of that is language-specific, surely, not really anything to do with static or dynamic typing. That whole mechanism is quite straight-forward and has a well-prescribed mechanism in Swift, for instance, with protocols, extensions, and conditional conformance.
> OTOH, in a dynamic language, all of that work is just gone.
I mean, it's not just gone. There are trade-offs, of course there are; but the assertion that the work is gone also implies the potential associated errors are gone seems a bit ingenuous to my eyes.
Yeah, types can be a bit constraining — but at the same time, if you reason about them properly and according to the language's prescribed mechanism rather than fight them, it can and does become second nature. At that point, using types becomes as simple for some as not using types is for others.
If you want to advocate something like protobufs, that's a separate issue from static vs. dynamic typing. The point is that in a dynamic language you literally _don't have to_ reason about many types at all. Or you do it in a completely different way that doesn't involve holding the type in your short term memory.
This is by design. This is great for prototyping.
These languages are to programming what breadboards and wires are for electronics.
Of course, as your system grows, you start to want static checks, or having your schematics on a PCB. But at the very start, tweaking a piece of Python code, or a breadboard, is easiest. Then, of course, you may not want to afford a a rewrite to rust, or would put your breadboard in a box and ship it :)
I've been working on large scale python systems for about 10 years. For me, the most cost effective checks as a system grew have been asserts.
They allow extremely sophisticated pre/post condition checking (partly helped by pythons flexibility), they're cheap to write, they don't take up much space, they catch a shit ton of bugs, they clearly highlight the source of the bug, they work together really well with higher level tests and I've almost never had them trigger in prod (so the fact that they technically could, as static type fans keep reminding me, doesn't really bother me).
For me, asserts and integration tests are what make large scale python systems a pleasure to write.
It's absolutely not the same with OCaml, F# and lately, TypeScript. And I don't think I write the prototype in more time in this languages (if we are talking about something that takes up days, not minutes, of course). But the experience some months down the road is completely different.
No, you don't. You need unit tests to verify behavior (values) whether or not you have static typing (except for output types with only zero or one values). Now, it's true, that having such tests also verifies, at no extra charge, things that Go’s type system would verify, but without the additional effort of type annotations. But there's no added cost there, it's a net savings.
Sure, you have to deal with more cases in running code if they aren't foreclosed by static guarantees, but you are either adding the thinking to code static guarantees and you are doing the thinking to code tests, and I've seen overpermissive types from too-shallow thought on that (even when it's not due to insufficient expressiveness in the type system) plenty of times to not think its substantially less of a risk than inadequate testing.
I'm not against static typing; I definitely think it has an important place in the toolbox, butnmy experience doesn't align with the idea that static typing is universally better for rapid prototyping.
Now let's look at the nature of programmers. What mistakes are they most likely to make on a regular basis? Well they are human, and they are whacking these big fingers at a keyboard. Maybe typos? Also programmers are bad at spelling, from the source code I've read.
Now could a unit test catch all typo-led mistakes? Well I doubt it. Not for a big program with 10's of KLCs.
Does typing help? Yes - and I'd say typing speeds up programming by allowing autocomplete of code (because of less er... typing as in the finger kind). Since the IDE knows the type it can help you find the write method.
Halt != bug, so this problem doesn't really apply.
You can write provably correct software, even if we still haven't solved the halting problem.
Autocompletion is available in numerous IDEs and editor plugins for many popular dynamic languages (and some dynamic language REPLs, too); static typing certainly can provide a source of data for autocompletion, but it's not necessary for it.
edit: as pointed out - I need type checking on my words
Moreover, they're typically able to do more sophisticated checks than most languages (e.g. similar to the kind of sophistication of Haskell's type system).
(yes, I know what is going to be replied... IME, they actually fail in prod vanishingly rarely)
Catching typos is an important part of what unit tests do with statically typed code, since typos result in value errors at least as often as type errors.
Ps and luckily, even with dynamic languages any decent IDE will catch most typos
I don't challenge the benefits of static typing for more ambitious projects with a constantly updated codebase and multiple developers, or the usefulness of IDE with large codebases or frameworks.
I wasn't disputing this in my previous comment, but I do actually disagree with it at this point. I haven't found a use-case for a dynamic language in many years. I cringe at the thought of working with them now.
> vanilla JS in a text editor is the best tradeoff
This defies my understanding of the word "tradeoff". It costs nothing to fire up a full IDE instead of a text editor, so why not use one? It comes with autocomplete at the very least, but also static analysis of JS.
If something costs nothing and has a benefit, it must be the best tradeoff, right?
> Any bug that would have been catched with typing will obviously be catched visually.
Catching bugs at runtime is slower by definition than catching them while you're writing the code in the first place. Why make things harder when great tooling is a few clicks away?
> I haven't found a use-case for a dynamic language in many years.
That’s actually my initial point. The use case is small scope projects, where you get the benefits of dynamic languages (faster and easier write and easier to read) without the maintainability cost and where you’re unlikely to ever get a type error.
Edit: concrete example: Standalone web page with a GET request to get some data, create a chart, and some user events like mouseover. No build tool chain, plain JS. I don’t see how I could benefit from types and what kind of errors would be prevented, but maybe the overhead is now very low with recent tooling and I should re-evaluate.
This seems like a rather extreme statement given the number of people in the mathematics and science communities that find plenty of use cases for Python/Scipy/Numpy/Pandas/Tensorflow, etc.
What language ecosystem would you suggest for those people?
Another aspect of that which I have noticed is that so many fans of dynamic languages refuse to use IDEs. They'll even state it simply as "I don't want to use a language that makes me use an IDE". For me, the IDE is like a second part of my brain. I use Eclipse which does incremental compile at every keystroke and I see errors, warnings, autocompletes, hints etc in real time. This removes a huge amount of the ergonomic barrier they are talking about (having to save your file, run a compiler, only then see the errors, then find that line in the file, etc ...). But they are stuck at "I don't want to use an IDE" ....
My own theory is that we likely underestimate how much of this might just be fashion. I don't mean this dismissively or to suggest that the mood swings over typing in the last 20-odd years have been without technical basis or impetus. Just that, you know, at some point people liked bellbottoms or Members Only jackets and later they didn't.