Exactly, I get quick and precise code completion, I catch plenty of errors beforehand etc. I'd say I'm about 10x as productive in C# as in Python, with similar amount of experience. Python only shines when there is a library that does something really well that you need. For me any productivity advantage in Python is from lots and lots of libraries.
Also in terms of maintainability, I find my C# code easy to read and modify a year later when I've forgotten completely about it. In Python I need to rescan all of the types into my head until I can understand what the program does.
I mean with var and dynamic, C# offers everything you need for duck typing efficiency, while preserving the very important statically typed interfaces.
Each task has it's set or preferred tools, to write a quick command line tool to bridge a gap between two systems and automate our workflow a bit further python is just perfect for me and much more productive than C#. However to create a system with a team of dozens spanning the whole skill gamut I feel much more comfortable having the compiler as an active member of my team policing the architectural vision for us. In which case C#/Java will win hands down.
If however speed and responsiveness are prime worries then I'll roll up my sleeves and bring C/C++ in. sure my productivity will take a hit, it will likely be harder to maintain as well but if this effort makes the difference for my customers in their productivity then it's well worth the investment.
Often times the final system will be a mix of all these technologies, each used where it's strength shines to maximise their impact.
Yet the technology is worthless when faced with inadequate code structure and system architecture. Coupled convoluted code will be a PITA no matter the language used.
Couldn't that be solved with sane variable naming conventions and docstrings/documentation?
That is not where productivity sinks are. They're in build systems, package managers, existence of good libraries and ease of debugging as well as limiting number of accidents while coding.
dynamic f = x => x * 2;
Duck typing doesn't seem to be offering everything.For example: dropping into a debugger (binding.pry / pry_remote in Ruby) to write code interactively in the context of the application, transferring that code to the source, and continuing on with my next fragment of functionality with a refresh of the web page, no recompiles or restarts required. You can do this with some difficulty and certain caveats in a statically compiled language, but only if things have been set up in just the right way, with automatic dependency recompilation and reloading, hot code replacement, etc. And even then, there are limitations on the kind of code you can write in the editor, depending on the underlying technology. Typically you can't create whole new classes, or introduce new fields etc.
Or consider levering up your language. Dynamic languages often give you the ability to create "new" syntax via expressive literals. This opens up more avenues for declarative programming; construct a data structure that models your problem more directly, and then write code that interprets the data structure. You want extremely lightweight literals for this, readable with minimal ceremony, including lambdas for when you need an escape hatch, a little pocket of custom code embedded in the bigger data structure. Statically typed languages have little context to infer types for such lambdas, unless they can generalize from the operators applied, but then you have a generic function, not a piece of data. So you end up infecting your DSL with type annotations and cruft, and before you know it the whole thing is hiding the forest behind the trees.
Thing is, if you aren't used to using these tools, you might not even know they exist, and you may be missing out on productivity you didn't know you could have.
Personally, I've never met a static language that was remotely close to dynamic in terms of productivity...but a big portion of that is the sheer volume of extra code necessary in a static language combined with personal workflow (as you describe).
What static languages thrive in, however, is maintaining old code that was written by somebody else.
Who uses straight C? C++ and C# have operator overloading. You can overload any of the operators for a class and get sane semantics for addition, subtraction, equality, etc.
You've been able to do that with ASP.Net MVC for years. You can change your views hit save without having to start over.
But on the back side, why is "restarting" such a heavyweight operation? If I'm iterating over a piece of back end code that takes a while to get to debug, I use the testing framework as a harness to run small bits of code - not a real unit test just an easy way to setup the prerequisites to step through a piece of functionality.
This opens up more avenues for declarative programming; construct a data structure that models your problem more directly, and then write code that interprets the data structure.
Even C++ has a level of reflection these days. But why wouldn't you be able to do that with c# and attributes or Java with annotations?
You want extremely lightweight literals for this, readable with minimal ceremony, including lambdas for when you need an escape hatch, a little pocket of custom code embedded in the bigger data structure. Statically typed languages have little context to infer types for such lambdas, unless they can generalize from the operators applied, but then you have a generic function, not a piece of data.
C# has had type safe lambda for a decade. And as far as a generic function, you can define a lambda/Linq expression that can be reused and that can generate c# IL code at runtime, a MongoQuery when using a the Mongo driver or SQL Expression when using Entity Framework. Ling expressions and the expression trees they generate are very powerful, versatile and typessafe.
As you say, you generally can't add new fields or classes, but it's not clear to me how important that is? Doing a full restart occasionally is not that big a deal.
I am skeptical about the long-term maintenance of systems with custom DSL's. Unless it's something popular like JSX, it seems like you end up with your own language dialect that nobody else understands?
I've also experienced an unexpected benefit in that it is a lot easier to read other people's code since types are explicit and easy to see.
"Statically type languages prevent a lot of bugs and allow for a lot of automated provably correct refactorings that simple cannot be done with a statically typed languages." -- not true, Clojure is trying to do that with Clojure.spec and specifications being checked at runtime can get you closer to things you could have automatically proved correct only with languages with dependent types, nothing against statically typed languages but I feel that your sweeping generalizations hurt the point you are trying to make.
It was very interesting to see that Clojure projects were among the least buggy of all languages represented. The report looked at a variety of factors to explain "bugginess", including typing and LOC, among other things. The conclusion in Clojure's case was that the very low line count necessary to write Clojure programs was vastly more influential on bugginess than for languages that were statically typed.
And I suppose that's true: a language that requires you write very little code is going to produce programs that are easier to write (and thus maintain), regardless of whether it is statically or dynamically typed.
That said, I have never seen any study like this where I didn't have some serious disagreement over methodology or conclusions. (Ok, except for that one on Peopleware.) It is a hard subject, and running a study over "the population of GitHub" is problematic by itself.
That puts at advantage both languages that reduce line count, and languages that make it possible to abstract the plumbing into a generic library (if it's not generic, it's part of this one project). I think mostly everybody has that same impression.
Still, we shouldn't pretend that impression is a scientific certainty.
"Checking at runtime* is exactly the problem. Why would checking at runtime be more reliable than compile time?
If you use metaprogramming (i.e. Lisp macros, Scheme/Racket macros, Clojure macros), then the only way to have truly reliable checks is at run time.
In fact, for debugging and testing, it's much better to have a runtime able to do many things (such as live patching of the code).
Contrary to a language like C or C++, where the compiler produces machine language from the source and then goes out of the picture, in most of the Lisp family languages your program runs along with the runtime; this runtime also contains the compiler itself and is usually big on features; it will not just catch the type errors, it will show you exactly all kind of important information about any, error and will allow you to correct the error if you like, without having to recompile and start again.
I use C#. When you create a Linq expression, you get type safety and it "compiles to" an expression tree that is converted to its runnable form at runtime -- in my case a Sql statement, a MongoQuery, or C# code. It really comes in handy. Theoretically I could change my backing Store from Mongo/C# driver to Sql Server/EF without changing any of the queries. I do this all of the time when unit testing, I substitute an in memory list for a database table and still get complete type safety.
That is a misconception. The macros output a target language and that can be checked. Plus macros can do their own checking before that. A macro expander can perform some static checks. In TXR Lisp, warnings about unbound functions and variables come from the macro-expanding code walk.
The expressions x - y and y - x often have exactly the same type tree, but calculate a different value. Same with f(x, y) and f(y, x) when the parameter types are the same.
To actually catch real bugs such as this with types, you have to make such a fine grained use of types that programming becomes nigh intractable.
If x has type minuend<integer> and y has type subtrahend<integer> then, x - y is well typed, whereas y - x isn't. Problem is, the values come from some other context in which their minuend and subtrahend are meaningless (and, in any case, are not involved).
The kinds of errors that are found by static typing in practice are those that, under dynamic typing, will only sneak into production if the code is not tested at all. I.e. if the code had been executed even once with any inputs, the problem would have showed up.
Plus, if you assume that "dynamically typed" language means "language with no static checks", you're arguing against a strawman of dynamic typing. That is even more ridiculous if you're pitting True Scotsman's state-of-the-art static typing against this dynamic strawman.
(Of course, some dynamic languages in widespread use do more or less look like that strawman; however, the best examples of dynamic typing are languages which are considerably informed about type prior to executing a program, like the SBCL dialect of Common Lisp, for instance.)
Typing has its place but the idea that it catches all bugs at compile time certainly isn't true; the cycle of running the app itself, running its tests, is just as important for statically-typed code as dynamically typed.
Languages that have a sophisticated contract system* (like Clojure's Clojure.spec library) have a bit of a leg up in my opinion. You can much more precisely explain and control and test for the specifics of all values flowing through your system well beyond merely what type they are.
-- * Clojure's devs don't refer to spec as a contract system because it is quite a bit more versatile than that, and some are actually using it for compile-time checks, compile-time generative testing, and other nifty things that aren't covered by a "contract" system.
Of course it can. The idea behind a decent type system is to make illegal states unrepresentable. You define a type which contains a value between 0 and 1 (lets name it `probability`) and make a function `float -> probability` which checks the range. That way, everytime you'll see `probability` in your code, you will know it is in the right range.
The problem with spec is that it can only verify what it sees on run time, whereas a static type system by definition has 100% coverage over your entire program.
Compile-time and run-time floating point numbers constrained by precision and range. It's been a long time so I'm just going to include the examples in that document:
type Coefficient is digits 10 range -1.0 .. 1.0;
type Real is digits 8;
type Mass is digits 7 range 0.0 .. 1.0E35;
subtype Probability is Real range 0.0 .. 1.0;
These will be checked (to the limits of the ability of the system) at compile-time, and by default enforced at run-time. You can disable the run-time checks if you want for the sake of performance. The compile-time checks will "fail" to detect things where computations beyond the compiler's ability to reason might produce an invalid value.I can't think of any typed language I've used or seen in active modern professional work where you could validate a value as part of a type at compile time.
https://docs.microsoft.com/en-us/dotnet/framework/debug-trac...
Yes and no, because nothing prevents you from passing the wrong kind of value in an untyped language unless, well, you check for the runtime type yourself in the function and reject that. And you can only do that at runtime, at which point you'll not be able to immediately see where you're passing in invalid data accidentally unless you hit this exact branch in your code. So actually just the type tagging is quite useful already.
Also, it looks like more powerful dependently-typed languages like Idris[0] (and probably also Agda and Coq) can encode this information in the type system.
Actually, it can, using dependent types :) I'm a Python fan, but Type-Driven Development with Idris has been a very interesting read.
I also wouldn't be surprised if Clojure tends to do surprising things in this area with taking Clojure.spec more to the compilation stage; some of the most interesting research into type systems has actually be done by lispers (i.e. Typed Racket, Core.Typed) and there is active interest in it among the Clojure community.
(For a number-type that is limited to eg be between 0.0 and 1.0):
Ada?
Sure, it can; many common numeric types include specific ranges, the fact that the range 0-1 doesn't match any common floating point type doesn't mean that it isn't one that could be a type.
Can you give me a mainstream language example?
Yes, it can! --- but you don't necessarily want to confound your program with such a thing.
The old adage about the cure being worse than the disease applies.
Ok, how? Other than an Ada example, or dependently-typed languages that aren't use in production, can you offer an example?
Every time someone gives you an answer, you modify the question to exclude it. Eventually you will have excluded everything that refutes the fallacy in your original post, but it is still there.
subset Positive of Int where * > -1
OTOH, while some Perl 6 supports some static type checking, subsets of this kind seem to support only dynamic, not static, enforcement. case class Probability(value: Double) { require(value <= 0 && value >= 1, "probability must be between 0 and 1" }I personally tend to do something much closer to TDD, and break my code out so I test each part with its own test rig. So I see when I do dumb things that need re-work. Again, typing is pretty much never an issue there.
While this is all anecdotal, I personally have found that enforcing boilerplate type defs actually increases my coding error rate. More LOC gives you more opportunities for errors.
FWIW, Julia (http://julialang.org) is IMO Python done right, and it is strongly typed. But done in such a way as to not actually get in your way most of the time. Like Python it has a great REPL, fantastic FFI, rapidly growing module list, very active development. Unlike Python, it has no issues with whitespace[1], is compiled (JIT for now, though static is planned from what I understand), is very fast, has multiple dispatch and a strong typing system.
The real point the article was making is, programmer time is the most valuable thing to optimize for. I completely agree with this, even though I disagree that Python is the right tool for (most) every job.
There are better tools IMO, which allow me to be far more productive, and spend as little time as possible on worrying about types ... which are rarely ever a concern for me.
I thought his comment about string processing was interesting, though he completely ignored Perl in the mix. I've found string processing in Perl to be almost trivial. Its harder (far more verbose boilerplate) in Python, and gets worse in other languages. Perl6 lets you embed grammars and write parsers without resorting to external modules (Marpa::* in Perl5). Takes building string processing, parsers, etc. to a whole new level, while making you even more productive.
But, as the author had noted, he is a pythonista. Everyone has their biases (including me). I want to code correctly quickly, and not have things that shouldn't get in my way, get in my way. Including type systems, massive boilerplate/odd syntax rules, etc.
[1] It is 2017 ... structure by indentation is somehow, still a thing. It shouldn't be IMO.
Yeah, C++ typing system sucks. Just don't think this generalizes to other languages.
Go: map[string]int64
Java: HashMap<String, long>
Rust: HashMap<&str, i64>
I think Haskell is one of the only statically typed languages which doesn't really care about algebraic typing. And yes, if you're talking about static typing, you get the baggage of dealing with algebraic typing to go with it as a rule. You can't arbitrarily separate the two until more languages follow Haskell's route.
OTOH, a language smart enough to (correctly) infer this (Perl, Julia, etc.) generally won't have a problem with this, and will handle these issues for you. Julia still allows you to be very explicit on type, and force a hard type specialized code. The benefit in this case, with type specialization, is that it can generate far more efficient code for the specific logic.
That latter argument is, to me, the only real benefit of typing systems that I've personally encountered. I know people throw studies around claiming "fewer errors with stronger type systems", but ... to be honest ... I have not experienced this. Rather, in stronger typed languages, I spend more time hunting down type impedance issues than logic issues.
This seems to not be a positive benefit to me. I may be alone in this regard, or not. I don't know.
I do know that, like programming languages, editors, operating systems, this view point (pro/anti strong typing) tends to take on a religious overtone in the sense of people taking a position and digging their heels in over this. Elevating this aspect as an important point in an overall platform decision process (what should we develop in), when, maybe, it shouldn't be.
In an odd way, I've seen this in the industry for a while. There were previous incarnations of this. Like Dijkstra's famous "goto" comments[1]. I don't necessarily agree with this, and I argue that people can write bad code in any language.
Typing systems ostensibly are there to help us write less bad code (all code is bad and buggy, anyone telling you otherwise is trying to sell you some swampland). But once they get in your way, and you start spending inordinate amounts of time dealing with typing issues, you have to ask whether or not they are helping or inhibiting.
Put another way, anything that helps should, actually help, without adding significantly to the burden. Apart from what Julia does, and other dynamic languages do ... that some people write great code in ... I've not seen/used many other systems that strive to get out of your way while working.
Typing systems do not automatically make code better. Absence of typing systems do not automatically make code worse. This is a function of the algorithm, the implementation logic, etc. How disciplined are you as the developer in making sure your code is clear, concise, and actually reflective of the problem you are attempting to solve ... things that help with that are welcome.
[1] http://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pd...
Let me introduce you to Racket. It started out in life as Scheme, and became something more.
DrRacket, it's IDE, is fantastic. The main reason why? You can trace every function call, and every macro, to its definition with a quick mouse-over, even if said definition is in another file or library, even if said definition is only conditionally created.
There isn't really a reason a dynamic language can't do tracing like that, or mass renames across files... Because some already do.
You can use comment annotations to typecheck with mypy in py2
And yet it was common understanding just a couple decades ago. And not just from naive "script kiddies". Read what PG writes about Lisp in ViaWeb for example.
And it's not because we know we have faster to develop static languages, type inference, faster compiles (and also not because of Haskell etc). Those apply to insignificant minorities using Go and Haskell (insignificant compared to the huge hordes using Java, C#, Python, PHP, C/C++, and JS).
It's because of the fad mostly -- JS transpilers and everybody else going for moar types.
>Statically type languages prevent a lot of bugs and allow for a lot of automated provably correct refactorings that simple cannot be done with a statically typed languages.
Which is almost irrelevant when it comes to produce something -- you can get it to market faster with those bugs than with the extra rigidness.
You meant to write 'dynamically'. Not too late to edit!
Correction:
Statically typed languages prevent a lot of the bugs that total beginners to programming would make; while they are useful in preventing the one, two or tree trivial, easy to solve bugs that a seasoned programmer would make once in a week.
They are also not useful at all to prevent the real-life, critical bugs that have nothing to do with types at all, and that are the ones that will negatively impact the product the most and will take serious time to get fixed.
You know what static typing is good for? It has nothing to do with "preventing bugs". It has to do with performance and compiler optimizations.
Most bugs are about types, just most languages’ type systems are too weak to express the logical types relevant to many important bugs (and, if they weren't, then you'd probably have to worry more the about bugs in the type-level code.)
Ok, so you are writing a function for a fast approximation of ATANH using IEEE floating point standard.
In type: IEEE floating point number
Out type: the same.
The function will report an incorrect value (bug), yet still be perfectly type safe.
Tell me how the magical type checking of the super-good-type-system programming language you propose will catch the error and thus prevent the bug.
And also, we're discussing about real-life problems, tackled with existing programming languages, not languages that don't exist yet.
Not even Haskell, with it's great type system, will help you in such cases. Not in the bugs that are caused by a flawed understanding of the business model, the business rules, or of the system as a whole.
On the other hand, the idea that dynamic languages are more productive than static languages are laughable. Statically type languages prevent a lot of bugs and allow for a lot of automated provably correct refactorings that simple cannot be done with a statically typed languages. You can't even reliably do a "find usages" of classes using a dynamically typed language.