My disagreement starts with the beginning of this:
"A large class of errors are caught, earlier in the development process, closer to the location where they are introduced."
I would re-state this as:
"A large class of errors are introduced, which otherwise would not exist."
Consider dealing with JSON in Java. Every element, however deeply nested, needs to be cast, and miscasting leads to endless errors, elsewhere in the code. Given JSON whose structure changes (because you draw from an API which leaves out fields if they don't have data for that field) your only option is to cast to Object, and then you have to guess your way forward, figuring out what the Object might be.
Consider the Salesforce clone of Java, Apex, which I have had to work in this month.
The if() statements here are the same one's that I would have to write in Ruby or Python or PHP, but meanwhile I've had to do a bunch of other, useless work:
public Object deserializeJson(String sandi_data) {
System.debug(sandi_data);
Object objResponse = JSON.deserializeUntyped(sandi_data);
if (objResponse instanceof Map<String, Object>) {
Map<String, Object> mapResponse = (Map<String, Object>)objResponse;
List<Object> dataList = (List<Object>)mapResponse.get('data');
if(dataList == null) {
String err = 'The Sandi API field for data was null';
System.debug(err);
ApexPages.Message msgErr = new ApexPages.Message(ApexPages.Severity.ERROR, err);
ApexPages.addmessage(msgErr);
return null;
} else if (dataList.isEmpty()) {
String err = 'The Sandi API field for data was empty';
System.debug(err);
ApexPages.Message msgErr = new ApexPages.Message(ApexPages.Severity.ERROR, err);
ApexPages.addmessage(msgErr);
return null;
} else {
System.debug('dataList:');
System.debug(dataList);
return dataList;
}
}
return sandi_data;
}
I'm leaving out the code that is downstream of this function, but it is full of more of the same: guessing at fields, guessing at how they should be cast, using if() to guard against null or empty. Tons of unnecessary bloat. Lots of easy errors to make.
Again, some of the if() statements need to be made in Ruby or Python or PHP, but the rest of it is just pure bloat. Verbose, unneeded and unhelpful.
In a dynamic language I could simply work with a deeply nested data structure of maps and lists, and I'd handle the casting at the very end of the process. In a dynamic language, I could treat everything as a string till the very end, and then cast to integers or dates or floats or strings as needed. In a dynamic language, I could write the code faster, with less errors, and with less code.
Static typing does not live up to its promises.
[ Edit to add ]
We have no control over the API that we draw from. We are drawing from the API of a different company. I wish they didn't use JSON. If they have to use JSON, I wish they at least enforced a consistent schema. But they don't. And that is why static type checking fails: because the real world is chaotic, and when you have t...
Have you considered the possibility instead that JSON is not an appropriate serialization protocol or storage mechanism for the problem to be solved? Or that it isn't being used effectively/correctly (that is, it isn't being parsed correctly, e.g. with a library or some other mechanism that makes checking underlying types less cumbersome)? I noted in a comment downstream from here that plenty of C and C++ JSON libraries offer very easy parsing (e.g. "json_get_int(element, key)") that fails early. I almost don't even think about JSON twice if I have to use it as a data format in a C++ program I'm working on. It's just another library to link against and use almost effortlessly. Doesn't Java, or this Salesforce implementation of it, have something similar?
Furthermore every dynamic language I've used has had tooling spring up, e.g. in the form of linters, that includes at least some basic checking for type mismatch where possible. I'm not so sure static typing leads to problems. It isn't problem free--no programming language or paradigm is--but I do think the lack of it leads to some problems that are easily avoided without too much extra overhead.
We have no control over the API that we draw from. We are drawing from the API of a different company. I wish they didn't use JSON. If they have to use JSON, I wish they at least enforced a consistent schema. But they don't. And that is why static type checking fails: because the real world is chaotic, and when you have to interact with that real world, you are often forced to do so dynamically, because of the mistakes that other companies have made. The real world is dynamic.
Ok, so they created a problem by using JSON without schema.
Now, instead of fixing problem by enforcing some schema you propose to use dynamically typed programming language which won't solve the problem but instead will make it spread thorough the whole codebase.
At least static typing limits the damage to the code that directly deals with parsing JSON.
The real world being dynamic has almost literally nothing to do with this issue. The concepts are completely unrelated.
Although I'd agree that I find development with dynamically typed languages a bit more chaotic than static or strongly typed ones. And not necessarily in a fun, good way, either.
In Java JSON is horrible for serialization because of Java behaviors (your best option is Object). That doesn't mean JSON is horrible for serialization, in general. Fields do have types (int, string, object, array) making it slightly better than arbitrary serialization.
I haven't used Java in a long time, so I don't what the library ecosystem for JSON is, but with both C and C++ there are plenty of parsers that make type casting and working with JSON data about as easy as in python. The difference is usually that the type of the underlying data one is interested in working with is explicit and, when the JSON data is malformed is caught earlier (at cast, as opposed to later, when trying to, e.g. add a string to an int in sane languages that break when that happens).
'Everybody else is doing it' is insufficient justification to keep on doing it.
As it happens, I agree that JSON is probably a long-term mistake, although it is definitely an improvement over XML, the previous player in that space.
That Salesforce has a relatively broken variant of a less-than-ideal statically typed language is less a problem with the idea of static typing than with Salesforce's crappy implementation[1].
There are less awful ways to deserialize JSON in other languages. In C# with JSON.Net, you have options ranging from very dynamic-style handling, all the way to fully-typed deserialization into POCOs, for example.
[1] I have no love for Salesforce; it's a mess. Their weird custom dialect of SQL for a REST API query language is also buckets of fun.
I think the problem in your example is mostly related to the fact that Java syntax is just horrible for dealing with JSON - not really an issue of typing.
If Java were conceived of today - I think it might look a little different with respect to this.
I'd like to point out that there are many areas wherein fluid typing might help a little bit, especially on the UI.
Have to make a class for 'every little thing' gets cumbersome.
I wish there was a 'lighter typing' opportunity in some cases (schema-ish JSON).
But I agree that in the long run, static typing is really the way to go for the most part.
Elm is similarly awful at decoding json, and was what stopped me from trying to use it. I mean, it'll get the job done, eventually, but it's just so much additional work, compared to just json.loads() in python.
Java is fine at decoding Json if you know what the fields are going to be. It's not like creating an object with the fields you want is very difficult and then libraries will deserialize it easily. It's also not hard to create a tool that will generate a POJO class from a string of Json. It doesn't really take time so the only problem is that it is yet another class on the classpath
Java definitely makes it harder to work with json but the only problem I run in to is that I need classes (which I just autogenerate) for every json object. Serializing and deserializing json is a one liner once you have classes set up for them to deserialize into. Again, it is definitely harder than in Python and Javascript but I work with Java and json every day and it's generally only a huge difficulty if you need a strange edge case.
EDIT: also, java is bad at dealing with json if the api could return data in multiple different schemas and you don't know which one it is until you parse it but I've only run into this once and it's just bad API design.
Haskell's aeson library, which is probably the most popular way to use JSON in that language, doesn't require any of this complexity or typecasting ugliness. It can derive a typechecking parser for a particular JSON format from an ordinary Haskell data type. Your type becomes a JSON schema, and as long as the JSON you're parsing conforms to that schema, the parsing just works with a single function call.
The problem you're pointing out is not a problem with statically typed languages. It's a problem with bad languages.
There's casting happening all over in the dynamic code in this scenario. It's implicitly handling what would be error conditions or explicit casting in a static language. Whether that's a good thing or not is what's at issue (generally, no, it's not).
Your example is non-sensical.
It has nothing to do with a comparison between static types vs dynamic types, it is only the usage of an awful JSON library in a not so nice language.
In F# you even have Type Providers for JSON that give you auto-completion in the IDE:
type Simple = JsonProvider<""" { "name":"John", "age":94 } """>
let simple = Simple.Parse(""" { "name":"Tomas", "age":4 } """)
simple.Age
simple.Name
You don't need to have these issues. If you have a field that is optional, you can model that with the Optional type in Java 8. So I would model your dataList as Option<List<Something>>. If I cared that a list is definitely not empty I would create a type called NonEmptyList to model that. This is very typical is Scala, which is the language I use most often.
You seem to be focusing just on the (admittedly problematic) problem of parsing outside and possibly invalid input. This is hard, especially if the API is poorly defined.
But this is hard with any typing system. Using dynamic typing will just hide the problems under the rug, and they will explode in your face later on. Static typing just made those problems explicit.
There are many benefits to static typing, beyond the external boundaries of the system, where it definitely lives up to its promises.
Abstract over and guard against external messy systems at the boundaries. Static types help with this just as much as enforcing invariants and consistency, at least with a good language.
Sorry to say this but you're talking out of your ####.
All the research from expiercal studies mostly all show that static vs dynamic languages all correlated with a Expertise reversal effect. This is for beginners not experience programmers. That at the start of the studies for the first 8 to 9 months novices have a negative correlation with the speed of development work with a statically typed language. After 8 to 9 months they gain a singificant boost from static type language.
Source to backup my statement and resource if you're interested @ Susan_hall
Gives the qoute:
`Our findings show that the receiver in 97.4% of all call-sites in the average program can be
described by a single static type using a conservative nominal type system using single inheritance.
If we add parametric polymorphism to the type system, we increase the typeability to 97.9% of
all call-sites for the average program.`
I could qoute other resources to backup my statements though if you want to have a skype coversation I'm up for a talk.
In Haskell you can generate a parser to a typed structure using library combinators and code generation:
data DataMessage = DataMessage { company :: Text
}
deriving (Show)
$(deriveJSON defaultOptions ''DataMessage)
data DataList = DataList { _data :: [DataMessage]
}
deriving (Show)
$(deriveJSON defaultOptions { fieldLabelModifier = drop 1 } ''DataList)
Which will fail properly when the data is malformed and in the real-world is even shorter, because you can set your data-type to protocol naming conventions and share them throughout.
Then there isn't much bloat. You wrote your types and generated your way to parse them.
Every element, however deeply nested, needs to be cast, and miscasting leads to endless errors, elsewhere in the code.
But these aren't needless errors. If something is not of the type that you expect, there should be an error, and static typing makes this more explicit. The verbosity is more to do with that particular implementation, not anything inherent to static type systems.
Why are we even still having this debate in 2016? The question is not if strong types are better but how to migrate existing codebases to those languages. I'm an OCaml guy myself but Swift looks pretty nice...
I started programming with PHP and JavaScript (both dynamically typed) then I started writing games with ActionScript 2 (dynamically typed) then I switched to ActionScript 3 (statically typed), then I got into Java (statically typed) and later C/C++ (statically typed) - So I spent a lot of time with both.
For the past few years I've been coding almost exclusively in dynamically typed languages - I did some Python (dynamically typed) but mostly a lot of JavaScript/Node.js (dynamically typed). I understand all the pros and cons, but for me personally, I am much more productive with dynamically typed languages than statically typed ones.
It was a long time ago, but I still remember clearly when I switched from AS2 to AS3 - I was writing games for Flash at the time; I did feel an improvement and I really liked the additional structure which types brought to my code. There was a certain satisfaction that came with defining fixed classes and interfaces and making use of polymorphism and various formal 'design patterns'. It gave me extra 'confidence' in my code.
In retrospect, after having spent years praising statically-typed languages, and then later switching back to dynamically typed languages, I think a lot of the benefits that I felt during my static typing phase came down to one simple fact:
"Statically typed languages force you think more before you do things" - This was really valuable early in my career when I had a tendency to rush things. However, now that I more fully appreciate how complex programming is (and how easy it is to break stuff), I am always very careful (regardless of the language).
Static typing for me has become a tedious process through which I no longer derive much value - Though it was really useful at a specific point in my career.
That said, I think there is some stuff (anything to do with low-level hardware/systems and optimizations) where statically typed languages cannot be avoided.
Also I wouldn't say that people who like statically typed languages are inexperienced - I know some very experienced engineers who are just addicted to that extra feeling of 'confidence' and structure which statically typed languages give you.
Forgive me, because I only have what you have said to work with. It sounds like this was a lot of smaller projects with smaller teams? If so, static typing increases value as project size and head count increases.
The more pieces of the project that you didn't write or otherwise have low knowledge of their inner workings, the more dangerous modifying code becomes. It's very useful to have something telling you that everything looks OK. That something can be testing, but why rely on writing good tests when we have formal proof systems available?
It's frustrating that this static vs dynamic 'battle' still goes on. The longer everyone thinks this is actually a problem, the longer we have to wait for innovations to happen. Look at the web in 2016, the technology is a complete disarray. Look at the game industry in 2016, where C++ is thrown around as the the cause and solution to all life's problems. A language created 33 years ago now with no intention of being used as it is today.
The advantage of static typing is obvious to me; the more my computer understands of my code, the less work I have to do. I can offload my memory and instead think about things that are more interesting, not be looking up Python obscurities on Stackoverflow.
But static typing is not perfect, and there is still more the computer could be doing if it had more understanding. That is where we should be focusing, that is where the real problem is.
With static typing the computer doesn't "understand" your code. It just automatically checks the correctness proof you've presented to it. And the correctness is limited only to certain properties of the program, that a given type system supports.
We can argue about the definition of 'understand' all day, but it's a simple fact that the computer has more of an understanding of the structure of your program with a type system, and thus can help out more.
> And the correctness is limited only to certain properties of the program, that a given type system supports.
In case there are readers not familiar with the current state of things, you can pick solid languages today whose type systems support a LOT. It is completely fair (and good) to point out that this isn't a panacea, but it can significantly help.
Regarding your last sentence, part of Alan Kay's motivation for OOP with SmallTalk was that he filt that type systems were limiting because they never anticipate all the possible types a program will need.
>The advantage of static typing is obvious to me; the more my computer understands of my code, the less work I have to do.
There's nothing about dynamic typing which precludes this.
There are also things which it is not necessary for your computer to understand but the rigidities of an excessively strict type system will demand it be told those things anyway.
I've dealt with dynamic typing vs static typing A LOT recently. In my experience static typing has a major complication that most programmers don't think about: datastructures. When you are dealing with a list in Python for example, you just throw whatever you are working with into that list. Whereas Java Generics and C++ Templates are very ugly because you can't just say "this is a list of stuff" you have to say "this is a list of a set of lists of ints". Not only is it ugly to write and read, it gets worse whenever you have any errors involving it. Ever seen a C++ compiler error message with templates involved? Other static languages like C don't even really try to deal with datastructures and just leave it up to the programmer to manually access the memory.
> Look at the web in 2016, the technology is a complete disarray
So that's pretty much been that way since the Java plugin for Netscape Navigator. That started the plugin battle which morphed into the mess we're in today.
> Look at the game industry in 2016, where C++ is thrown around as the the cause and solution to all life's problems.
Software written in C/C++ often have LUA/Python/Perl plugins to add scriptability.
> The advantage of static typing is obvious to me; the more my computer understands of my code, the less work I have to do.
Nope. Computers don't understand code, they just processes it and point out where your errors are, there's very little understanding. Type checking is really just rattling off a checklist to validate the type being passed around is the type that's expected.
Newbie starts with C++, moves on to Haskell after a very brief dabbling in some dynamic languages, and comes out thinking expressive static type systems are great. No great surprise, no real insight.
The purpose of programming languages is to bridge the gap between ideas and execution. The purpose of a type system is to prove something about the code; astute readers will notice that this is an orthogonal concern. Static types may be helpful; they may impede progress. It mostly depends on the application.
The chap's lack of experience makes him unqualified to expound in the way that he does. I'm sorry I read the article. I wanted to warn other people away.
> The purpose of a type system is to prove something about the code
The workaday purpose of these type systems is to write more of your intentions into the actual program code and get the compiler to enforce them on your behalf.
That may seem like a mere restatement, but the shift in viewpoint can give tangible benefits to someone who is new to ML-style languages and isn't convinced that making the compiler happy is doing them more good than the pain it's causing them.
Interestingly, most of these advantages are not specific to static typing, but derive from having a language that talks about types – even a dynamic one. For example, most of these advantages apply to Julia as well, a dynamic language that has type declarations, which:
- Lets you create typed collections so that if you insert the wrong kind of value, you get an error immediately, albeit only when code runs, not before;
- Has abstract types that serve the same function as Haskell's type classes, and allow you to inherit a huge amount of functionality for a very brief type definition;
- Serve as documentation of APIs;
- Allow performance similar to C/C++.
Another advantage that wasn't listed is that in languages that don't allow type annotations, you end up writing a lot of boilerplate type-checking code in libraries to give better error messages.
I didn't claim it was. That's one of the tradeoffs you make in exchange for a simpler, more forgiving programming model (and the ability to do serious work in a REPL or notebook). However, given that Julia programs can be mostly type inferred before execution, it is possible to check for this kind of error before running a program. There have been some projects to do exactly that [1] [2], and I suspect that once Julia reaches 1.0, we'll focus more effort on static analysis tools.
The point is that you don't need a static language to get many of the benefits of types, you just need your language that allows you to express type-based properties. One of the way to do that is to have a set of formal rules for deriving the type of every expression in a program, but that's not actually necessary.
The advantage OP was referring to was that you could force a type warning at all, as opposed to PHP, JS etc where typed arrays don't exist (notwithstanding Uint32Array and such).
The claimed benefit is getting the error earlier and "at the location the erroneous value is inserted." That's exactly what happens in a dynamic language with typed collections – but, as I admitted, not at compile time. Is it better to get an error at compile time? Sure, but most of the benefit comes from the error being raised at the erroneous insertion and not later when the value is used by some completely unrelated code. Hence, my claim of getting most of the benefit. Now there are certainly other arguments to be made about "highly effective program manipulations", but that's not the case being presented by the author here.
Those other "highly effective program manipulations" include refactoring and modifications which involve changing parameter and return types. While I understand what you're saying, to myself it's pretty clear that there's a wide gap between these two scenarios:
* Changing the collection's type and not knowing until runtime -- when your program crashes -- that it's being used wrong.
* Changing the collection's type and the program refusing to compile until all usage is corrected.
Because of this, I don't agree with the most modifier you're attempting to apply. The difference to me is confidence. In the former, how confident am I that I didn't miss a use-case on some branch? Considering that the program will crash or otherwise fail to operate, that seems like something I want to be pretty confident about.
It's very useful to have something telling you that everything looks OK. That something can be testing, but why rely on writing good tests when we have formal proof systems available?
These are exactly some of the advantages Perl6 has over Perl5. In Perl6 you do have types but they're optional. So you can be as precise or as imprecise (typed) as you like.
In D for instance you do have an auto type, yet it's still a type. An auto function that returns real or BigInt will trigger a compiler error. You need to convert the real to BigInt before any compilation can occur.
In my opinion there is a huge difference between I get an error in my compiler, and my user has a hard to reproduce error in a month when they are in the middle of a business critical task.
I've recently joined a team writing primarily in Clojure, whose proponents often tout repl-driven programming as a unique benefit to the language.
In a statically typed language (the stronger the better), aided by a good IDE, I don't need to be constantly executing my code against data during development; My editor is constantly validating my code, and when it stops complaining, my code will work. And months later when I or someone else uses that code in another part of the system, they won't need to execute that code to see how it behaves, as the types themselves provide documentation and as-you-code feedback.
You could argue that with PowerShell you have an entire .NET REPL. And I have actually used it that way, particularly when I run into some function that has bad MSDN documentation.
Let's say, "there is high probability that my code will work correctly right away". (This is especially true for Haskell.)
[Edit] To be fair, this is not only, and even perhaps not so much, due to the static typing per se but also due to the mental discipline the particular programming language may require from the programmer even to write code that can be successfully compiled.
Actually, based on my experience, what I found most remarkable about writing code in Haskell (which I admit I haven't done much of) is that I'd spend an enormous out of time wrestling with the compiler, but once I finally got all the type errors to clear out, the odds were surprisingly high that the code would "just work", which is not an experience I had with even other compiled languages (Java, C#, etc).
'course, it probably wouldn't work fast, and it was 50/50 whether I'd introduced space leaks, but...
It's a back-handed agreement. Static typing increases value with project complexity and head count. So yes, if you only ever do small tasks with low or singular head count, then dynamic typing probably works OK for you.
As complexity increases, the probability approaches zero that any code works exactly as desired on first execution. I'd still take that bet. Of course, I'd have to be able to use the REPL about as often as a someone uses a compiler.
I would say 'a statically typed language aided by a type inference engine'. My biggest bugbears with statically typed languages go away when decent type inference comes into play.
The author asserts that static typing allows the compiler to answer this question, but this only allows the compiler to spot type errors in advance. There are many other kinds of errors that are completely invisible to the compiler.
In a dynamically typed language, if I don't spot the error from reading the code, I must wait until runtime/testing to discover the error. This is also true for a statically typed language for every kind of error except type errors. Personally, type errors haven't been the kind of errors that haunt my dreams. I guess that's why I'm not enthusiastic about static typing.
You are right - in a tautalogical way - that type systems only catch type errors. However, in modern languages (including Haskell, Scala, as well as newer, more experimental languages like Idris), those type errors can be extremely powerful.
Many people assume that 'types' are simply primitives like Int and String, and that a type checker just makes sure you don't pass an Int to a function expecting String. However, it is possible to express far more powerful statements about your data using a good type system.
For example, you can express the idea of non-emptiness of a container, as mentioned in the article. Then you know that, say, taking the max element of a non-empty container is guaranteed to give you an element, whereas with a possibly-empty container you might not have any element at all, causing a null, or exception, or at least requiring an Optional type.
You can express safety properties such as a sanitized string vs. unsanitized. You can have a Sanitized type that can only be created by calling a sanitize function - which carefully escapes/handles any invalid characters - and then functions that might, say, pass a value into an SQL instruction can be typed to only take Sanitized strings. Now the representation in memory of Strings and Sanitized strings is identical, but by using different types and a certain set of allowed functions on those types, you can encode the invariant that a string cannot be inserted into an SQL query until it has been sanitized. Now your type checker can catch SQL insertion vulnerabilities for you. How's that for a type error?
First, when most people talk about static typing, they're talking about the near-useless version -- just types like Int and String. I think we agree there, so I won't mention it further.
Second, a dynamically typed language like Python has more typing information than some folks first assume. Python's AttributeError is quite similar to a TypeError. In fact, with old-style classes (v2.1 and earlier), many errors that are now TypeErrors were AttributeErrors. Calling len() on an inappropriate object would raise "AttributeError: no __len__". In many cases where folks talk about wanting a static type system, they really just want interfaces.
The Sanitized string example is a good counter-point because the interface needs to be near-identical to a regular string. I'm not certain a more complex memory representation (caused by defining a different class) would cause noticeable inefficiency. We're probably not doing vectorized operations on strings.
This brings me to my third point, that Python 3 has a similar split between two types: bytes and str. The memory representation is slightly different, bytes vs unicode, but the interfaces are nearly identical. Two differences would be decode vs encode and that getting an element from bytes (annoyingly) gives an int. The distinction between the two types is enforced mostly inside builtin functions, implemented in C. This was a big deal, causing backwards incompatibility, many flamewars, and we're still resolving it, though I think it's clear to most people now that Python 3 is the future.
Is it possible that the Python 2/3 split could have been avoided if we had a static type system? Perhaps, if we had multiple dispatch, the function signatures could have remained the same, avoiding backwards incompatibility... I'm just speculating here. My guess is no, getting rigorous about unicode would cause incompatibility regardless of the type system. I'll get back to the main topic now.
> Now your type checker can catch SQL insertion vulnerabilities for you.
This sounds useful, but a good interface solves the problem just as well. I'm a Pythonista (if you haven't noticed), so my example is PEP 249 that specifies a DB API for all database wrapper implementers to follow. It states that it's the wrapper dev's responsibility to implement a sanitizing string interpolation for the cursor's execute method.
My conclusion is that designing a good interface is important whether you have dynamic or static typing. Static typing errs on the side of safety, dynamic typing errs on the side of flexibility. Both can mimic the other. Arguing that one is better is like saying linear regression is better/worse than k-nearest-neighbors.
> First, when most people talk about static typing, they're talking about the near-useless version -- just types like Int and String. I think we agree there, so I won't mention it further.
I don't agree. Who is "most people"? Certainly not PL designers and not most of what I've seen here in HN. More importantly, it's also not what the article under discussion is saying, either.
> Static typing errs on the side of safety, dynamic typing errs on the side of flexibility. Both can mimic the other. Arguing that one is better is like saying linear regression is better/worse than k-nearest-neighbors.
In my experience, this isn't true. Modern statically typed languages have all the convenience of dynamically typed ones, such as REPLs and elegance, plus the safety of early warnings and the guidance that static types give you while writing your code (if you've ever written code like this, you'll know the feeling of working with building blocks that "fit" with each other). So you can have your cake and eat it, too.
Also in my experience, not having experience with these languages is what leads some people to think their type systems can only state trivial things such as "this is a String". They can do more. They can say things such as "this expression/function doesn't write to disk as a hidden side effect", which is useful!
Like a dynamic language with optional type hints? As I said, both techniques can mimic each other, with the corresponding tradeoffs. As you use more generics in a statically typed language, you're sacrificing safety. As you use more type hints in a dynamically typed language, you're increasing syntax clutter and decreasing flexibility.
No, not like a dynamic language with optional hints. "Optional" here is a huge disadvantage. For example, if a function lacks a hint which would indicate it's pure, is this because it's meant to be impure or because the programmer forgot to add the hint? No, in order to be useful, static typing must be on by default.
Like the other commenter says, generics actually increase safety: there are fewer assumptions (and therefore, incorrect assumptions, aka bugs) you can make when your functions are generic. Also, modern statically typed languages do not increase clutter by much, and can be very elegant and brief.
Yes, this is the point that's often missing in discussions about types. You can (and you have to work to) encode properties as types to get more value out of them. It's not about avoid mixing ints and strings.
> Personally, type errors haven't been the kind of errors that haunt my dreams.
The point of strongly-typed systems is that you can represent your constraints as types. This takes extra thinking and work, but gives you almost almost unlimited expressive power (ref: agda).
Simple example: meters and feet as different numerical types. When you multiply them, you get a silly unit (foot-meters) that doesn't fit with whatever you wanted (meters^2), and thusly fails compilation.
I haven't met anyone other than Haskellians that would create separate types for meters and feet.
I also wonder when you would make that distinction in the lifecycle of your application. I suspect not until you first encounter the bug of accidentally mixing units. If so, we'd be solving the problem at the same time, just using different techniques.
Hi! nice to meet you. I've used this in C++ to great effect. It's also in boost. I put it in anywhere I expect math on units that are easily screwed up. I don't retrofit them in. It's easy enough to have a meters_t typedef from the beginning. It also serves as documentation for the code.
"Type errors" go way further than "Damn, I passed a string in where I expected an integer". Types are a way of expressing aspects of your code. You can avoid race conditions, you can ensure a program's state is always expected, you can avoid race conditions, you can avoid design errors by encoding the contracts of your design into your types.
I think if you're used to a language like Java or C++ you may not see what types can really buy you, but that's because most languages have bad type systems.
Static typing is one technique for designing safe, easy to use interfaces. Depending on the language, there may be other tools that are just as effective.
Like what? I assume you're referring to testing. Tests are a way to ensure that for some set of inputs you will get some set of desired outputs. And this is assuming you wrote correct tests.
Types are far more rigorous - you can ensure, with types, that your code behaves in a certain way given any input. Yes, this extends to "design" bugs ie: not just crashes - you can write a type that ensures that an API can only be used correctly, for example. You can encode logic like "Don't allow unauthenticated users to access this content" into your type system - and you no longer need tests.
Of course, type systems don't prevent you from writing tests. They actually make it easier, you can generate test cases based on types, as an example.
I'm not claiming to not write tests. I am claiming that there's a class of tests that do not need to be written if a formal proof system is guaranteeing the results. I don't need to write a test to see that `Math.sin("banana")` behaves properly, because the type system can guarantee that this doesn't happen.
No, not just testing. Most languages have a variety of control flow tools, standard abstractions, and idioms that enable the creation of instantly-familiar interfaces.
The biggest issue with dynamic programming for me is refactoring code. I feel very confident when refactoring a static code-base. With a dynamic code-base it 's much more risky - to the point where I avoid.
Of course having great test coverage helps alleviate this, but it's very rare where a large project has 100% test coverage.
Sometimes behavior follows from the types. Combined with inference you sometimes wind up writing substantially less code (because it's basically being generated for you by a prolog program).
> Some tasks, especially around generic programming, can be very easily expressed in a dynamic language, but require more machinery in a static language.
I think this is not generally true and not a point against static typing, but rather against statically typed languages with poor support for generics. Java stands out as the poorest implementation of generics I have ever seen.
> For instance, a generic serialization library can be written in a dynamic language, without anything fancy, but providing the same thing in a static language requires more machinery, and is sometimes more complicated to use.
As a counterexample, have a look at NimYAML (my work):
http://flyx.github.io/NimYAML/
The examples there show how easy it is to provide the user with a generic interface for serialization in a statically typed language. The implementation does not differ much from what you'd do in a dynamic language: Provide a pair of serialization/deserialization handlers for each of [simple types (string, int, float, enums), array/sequence types, tuple/object/struct types, dict/map types, pointer/reference types].
Type-erasure was a crime against the Java community, perpetrated to make the JVM writers' job easier. Backwards compatibility was a poor excuse, as I can think of at least two ways to mitigate that without type-erasure:
* Introduce new generic types in a new namespace. .NET did this with `System.Collections` and `System.Collections.Generic`.
* Default unspecified generics to `Object`, including usage of generic types in bytecode tagged with pre-1.5 versions.
"This is sort of a subtle point: when programming in a static language, you always have a choice about what information you encode in the types, and how you encode things. ... In a static language, you do always have the option of building a less typeful API, where less is enforced by the types, but it is often tempting to spend more time encoding things statically (and then proving things to the typechecker) than would be saved by avoidance of potential future bugs. With experience, you develop a good sense for what is worth tracking statically and what to keep dynamic, but newcomers to static languages can make bad tradeoffs here, which in turn contributes to needless complexity in the language’s library ecosystem. "
This is a very important point that I've rarely seen talked about explicitly though I think all programmers develop a tacit sense of it.
It's an important point, but it can also be fairly well generalized to a lot of the "softer" points of program design. For instance, when to introduce an abstraction layer, and where the boundaries of the abstraction are. You could find-and-replace instances of "static typing" with "abstraction layer" into the quoted paragraph and it still makes perfect sense.
I think the concept of refactoring in dynamically typed vs statically typed languages is a double-edged sword comparison.
On one hand, refactoring code written in a statically typed languages is less error-prone - But on the other hand, such refactorings tend to affect more code than those of dynamically-typed code.
If your business requirements change often and refactorings are common, it can be a pain to keep having to rethink your code structure.
Dynamically-typed code is often easier to extend and modify. I find that with statically typed code, if you start messing with a small part of your code, sometimes you have to rethink your entire class hierarchy. With dynamic languages, your code can handle quite a few changes before it gets to a point were you need to rethink the overall structure.
I think the concept of refactoring in dynamically typed vs statically typed languages is a double-edged sword comparison.
On one hand, refactoring code written a statically typed languages is less error-prone - But on the other hand, such refactorings tend to affect more code than those of dynamically-typed code.
Dynamically-typed code is often easier to extend and modify. I find that with statically typed code, if you start messing with your code, sometimes you have to rethink your entire class hierarchy. With dynamic languages, your code can handle quite a few changes before it gets to a point were you need to rethink the overall structure.
One of my problems with static types in a lot of languages is that they're usually very limited in what they allow you to express.
For example: a function that takes an integer between 1 and 100. That's a straightforward constraint! Elixir is an example of a language that lets you express some of those kinds of constraints, by using guard clauses. Along with its powerful pattern matching, you end up with very nice code.
What are other languages that are known for these kinds of constraints?
It is usually still considered part of type checking. It's basically adding ranges to the types. For instance, `sin` will always return `float[-1, 1]`. If I have a function that takes `float[-10, 10]`, they're compatible. If my function takes `float[10, 20]`, then they're not compatible.
I figured it was relevant, since it relates to types as a whole.
Consider the following: I have a "rating" type, which is a whole number from 0 to 5. If you try rendering a view with a number outside of that range, that's a bug! Which means you probably made a mistake somewhere.
I want software to help me catch bugs. If something can't be confirmed with a linter or compiler, getting a good error message during runtime is also fine. Once I've established that I expect some value, I don't want to write extra checks to confirm that my expectations are met.
It relates to typing as a method of encoding invariants, yes. I was trying to say that it is unrelated to which "type" of typing. That is, I can perfectly imagine a statically typed language where you could encode such restrictions as easily as you do with Elixir and you would get the same result, a runtime error.
Regarding your rating example, you could encode that restriction in a type (or class in OO) and have a guarantee that once you have an instance of that type you no longer need to check for it's validity. Rendering a Rating would never fail at runtime, you'd get a type error first.
It's important to remember that the terms "statically typed" and "dynamically typed" cover a huge range of different language features. To have a good discussion about the tradeoffs, it's better to talk about individual features rather than "statically typed" or "dynamically typed".
For example,
Like puzzle pieces with shapes that we can observe fit together, we can think of types as specifying a grammar for programs that ‘make sense’.
All programming languages have a grammar for programs that make sense (it's specified by the parser). Algebraic data types give you a grammar for values that make sense. Separately, a type checker assigns types to expressions and checks that they're consistent. You can have algebraic data types without a type checker (e.g. Racket's 2htdp/abstraction), and you can have a type checker without algebraic data types.
You can also have constraints on values that are too complex to be easily checked at compile time (e.g. clojure.spec) or that can be checked at compile time but only incompletely (e.g. Erlang's -type and Dialyzer).
Anyway, I don't mean to be critical of the article: it does a good job covering the high-level tradeoffs. I just think people are too quick to generalize their experience with particular languages to entire classes of language features.
126 comments
[ 2.6 ms ] story [ 167 ms ] thread"A large class of errors are caught, earlier in the development process, closer to the location where they are introduced."
I would re-state this as:
"A large class of errors are introduced, which otherwise would not exist."
Consider dealing with JSON in Java. Every element, however deeply nested, needs to be cast, and miscasting leads to endless errors, elsewhere in the code. Given JSON whose structure changes (because you draw from an API which leaves out fields if they don't have data for that field) your only option is to cast to Object, and then you have to guess your way forward, figuring out what the Object might be.
Consider the Salesforce clone of Java, Apex, which I have had to work in this month.
The if() statements here are the same one's that I would have to write in Ruby or Python or PHP, but meanwhile I've had to do a bunch of other, useless work:
And then, downstream of this: I'm leaving out the code that is downstream of this function, but it is full of more of the same: guessing at fields, guessing at how they should be cast, using if() to guard against null or empty. Tons of unnecessary bloat. Lots of easy errors to make.Again, some of the if() statements need to be made in Ruby or Python or PHP, but the rest of it is just pure bloat. Verbose, unneeded and unhelpful.
In a dynamic language I could simply work with a deeply nested data structure of maps and lists, and I'd handle the casting at the very end of the process. In a dynamic language, I could treat everything as a string till the very end, and then cast to integers or dates or floats or strings as needed. In a dynamic language, I could write the code faster, with less errors, and with less code.
Static typing does not live up to its promises.
[ Edit to add ]
We have no control over the API that we draw from. We are drawing from the API of a different company. I wish they didn't use JSON. If they have to use JSON, I wish they at least enforced a consistent schema. But they don't. And that is why static type checking fails: because the real world is chaotic, and when you have t...
Furthermore every dynamic language I've used has had tooling spring up, e.g. in the form of linters, that includes at least some basic checking for type mismatch where possible. I'm not so sure static typing leads to problems. It isn't problem free--no programming language or paradigm is--but I do think the lack of it leads to some problems that are easily avoided without too much extra overhead.
Now, instead of fixing problem by enforcing some schema you propose to use dynamically typed programming language which won't solve the problem but instead will make it spread thorough the whole codebase.
At least static typing limits the damage to the code that directly deals with parsing JSON.
Although I'd agree that I find development with dynamically typed languages a bit more chaotic than static or strongly typed ones. And not necessarily in a fun, good way, either.
In Java JSON is horrible for serialization because of Java behaviors (your best option is Object). That doesn't mean JSON is horrible for serialization, in general. Fields do have types (int, string, object, array) making it slightly better than arbitrary serialization.
As it happens, I agree that JSON is probably a long-term mistake, although it is definitely an improvement over XML, the previous player in that space.
There are less awful ways to deserialize JSON in other languages. In C# with JSON.Net, you have options ranging from very dynamic-style handling, all the way to fully-typed deserialization into POCOs, for example.
[1] I have no love for Salesforce; it's a mess. Their weird custom dialect of SQL for a REST API query language is also buckets of fun.
If Java were conceived of today - I think it might look a little different with respect to this.
I'd like to point out that there are many areas wherein fluid typing might help a little bit, especially on the UI.
Have to make a class for 'every little thing' gets cumbersome.
I wish there was a 'lighter typing' opportunity in some cases (schema-ish JSON).
But I agree that in the long run, static typing is really the way to go for the most part.
It's a common challenge for Elm beginners.
It does the job but it's not nearly as nice as javascript or python. It's about double the code in Java and it doesn't read nicely.
I think it depends on what you're doing.
EDIT: also, java is bad at dealing with json if the api could return data in multiple different schemas and you don't know which one it is until you parse it but I've only run into this once and it's just bad API design.
Developing/experimenting/scripting is where it's painful.
The problem you're pointing out is not a problem with statically typed languages. It's a problem with bad languages.
But this is hard with any typing system. Using dynamic typing will just hide the problems under the rug, and they will explode in your face later on. Static typing just made those problems explicit.
There are many benefits to static typing, beyond the external boundaries of the system, where it definitely lives up to its promises.
All the research from expiercal studies mostly all show that static vs dynamic languages all correlated with a Expertise reversal effect. This is for beginners not experience programmers. That at the start of the studies for the first 8 to 9 months novices have a negative correlation with the speed of development work with a statically typed language. After 8 to 9 months they gain a singificant boost from static type language.
Source to backup my statement and resource if you're interested @ Susan_hall
Functional Geekery Episode 55 – Andreas Stefik https://www.functionalgeekery.com/episode-55-andreas-stefik/...
Secondly another study that was published for Polymorphism in Python from the website (neverworkintheory)
Polymorphism in Python http://neverworkintheory.org/2016/06/13/polymorphism-in-pyth...
Gives the qoute: `Our findings show that the receiver in 97.4% of all call-sites in the average program can be described by a single static type using a conservative nominal type system using single inheritance. If we add parametric polymorphism to the type system, we increase the typeability to 97.9% of all call-sites for the average program.`
I could qoute other resources to backup my statements though if you want to have a skype coversation I'm up for a talk.
Then there isn't much bloat. You wrote your types and generated your way to parse them.
You could test with:
https://gist.github.com/b30f6f09a737dcc980e052b0f3d2a39eBut these aren't needless errors. If something is not of the type that you expect, there should be an error, and static typing makes this more explicit. The verbosity is more to do with that particular implementation, not anything inherent to static type systems.
For the past few years I've been coding almost exclusively in dynamically typed languages - I did some Python (dynamically typed) but mostly a lot of JavaScript/Node.js (dynamically typed). I understand all the pros and cons, but for me personally, I am much more productive with dynamically typed languages than statically typed ones.
It was a long time ago, but I still remember clearly when I switched from AS2 to AS3 - I was writing games for Flash at the time; I did feel an improvement and I really liked the additional structure which types brought to my code. There was a certain satisfaction that came with defining fixed classes and interfaces and making use of polymorphism and various formal 'design patterns'. It gave me extra 'confidence' in my code.
In retrospect, after having spent years praising statically-typed languages, and then later switching back to dynamically typed languages, I think a lot of the benefits that I felt during my static typing phase came down to one simple fact:
"Statically typed languages force you think more before you do things" - This was really valuable early in my career when I had a tendency to rush things. However, now that I more fully appreciate how complex programming is (and how easy it is to break stuff), I am always very careful (regardless of the language).
Static typing for me has become a tedious process through which I no longer derive much value - Though it was really useful at a specific point in my career.
That said, I think there is some stuff (anything to do with low-level hardware/systems and optimizations) where statically typed languages cannot be avoided.
Also I wouldn't say that people who like statically typed languages are inexperienced - I know some very experienced engineers who are just addicted to that extra feeling of 'confidence' and structure which statically typed languages give you.
Cant't help thinking that that's probably something like 90% of code out there...
The more pieces of the project that you didn't write or otherwise have low knowledge of their inner workings, the more dangerous modifying code becomes. It's very useful to have something telling you that everything looks OK. That something can be testing, but why rely on writing good tests when we have formal proof systems available?
The advantage of static typing is obvious to me; the more my computer understands of my code, the less work I have to do. I can offload my memory and instead think about things that are more interesting, not be looking up Python obscurities on Stackoverflow.
But static typing is not perfect, and there is still more the computer could be doing if it had more understanding. That is where we should be focusing, that is where the real problem is.
I could say "python is better at understanding what the programmer wants to do". But I think you'd disagree with me.
The compiler is doing lexical analysis of the program being fed into it. That's it.
In case there are readers not familiar with the current state of things, you can pick solid languages today whose type systems support a LOT. It is completely fair (and good) to point out that this isn't a panacea, but it can significantly help.
There's nothing about dynamic typing which precludes this.
There are also things which it is not necessary for your computer to understand but the rigidities of an excessively strict type system will demand it be told those things anyway.
You know that they update the language regularly, and have a games sig? https://groups.google.com/a/isocpp.org/forum/#!forum/sg14
It could really use profiles. Strict mode or something, where the code should only use N "good practice" features.
https://dlang.org/spec/cpp_interface.html
So that's pretty much been that way since the Java plugin for Netscape Navigator. That started the plugin battle which morphed into the mess we're in today.
> Look at the game industry in 2016, where C++ is thrown around as the the cause and solution to all life's problems.
Software written in C/C++ often have LUA/Python/Perl plugins to add scriptability.
> The advantage of static typing is obvious to me; the more my computer understands of my code, the less work I have to do.
Nope. Computers don't understand code, they just processes it and point out where your errors are, there's very little understanding. Type checking is really just rattling off a checklist to validate the type being passed around is the type that's expected.
The purpose of programming languages is to bridge the gap between ideas and execution. The purpose of a type system is to prove something about the code; astute readers will notice that this is an orthogonal concern. Static types may be helpful; they may impede progress. It mostly depends on the application.
The workaday purpose of these type systems is to write more of your intentions into the actual program code and get the compiler to enforce them on your behalf.
That may seem like a mere restatement, but the shift in viewpoint can give tangible benefits to someone who is new to ML-style languages and isn't convinced that making the compiler happy is doing them more good than the pain it's causing them.
- Lets you create typed collections so that if you insert the wrong kind of value, you get an error immediately, albeit only when code runs, not before;
- Has abstract types that serve the same function as Haskell's type classes, and allow you to inherit a huge amount of functionality for a very brief type definition;
- Serve as documentation of APIs;
- Allow performance similar to C/C++.
Another advantage that wasn't listed is that in languages that don't allow type annotations, you end up writing a lot of boilerplate type-checking code in libraries to give better error messages.
The point is that you don't need a static language to get many of the benefits of types, you just need your language that allows you to express type-based properties. One of the way to do that is to have a set of formal rules for deriving the type of every expression in a program, but that's not actually necessary.
[1] https://github.com/astrieanna/TypeCheck.jl
[2] https://github.com/tonyhffong/Lint.jl
> For example, most of these advantages apply to Julia as well...
and then you listed it as an advantage.
It enables a whole class of highly effective program manipulations that are just unavailable to a non-statically-checked language.
* Changing the collection's type and not knowing until runtime -- when your program crashes -- that it's being used wrong.
* Changing the collection's type and the program refusing to compile until all usage is corrected.
Because of this, I don't agree with the most modifier you're attempting to apply. The difference to me is confidence. In the former, how confident am I that I didn't miss a use-case on some branch? Considering that the program will crash or otherwise fail to operate, that seems like something I want to be pretty confident about.
It's very useful to have something telling you that everything looks OK. That something can be testing, but why rely on writing good tests when we have formal proof systems available?
In D for instance you do have an auto type, yet it's still a type. An auto function that returns real or BigInt will trigger a compiler error. You need to convert the real to BigInt before any compilation can occur.
In a statically typed language (the stronger the better), aided by a good IDE, I don't need to be constantly executing my code against data during development; My editor is constantly validating my code, and when it stops complaining, my code will work. And months later when I or someone else uses that code in another part of the system, they won't need to execute that code to see how it behaves, as the types themselves provide documentation and as-you-code feedback.
You mean your code will compile and run. Whether it behaves as desired is completely unknown without testing.
[Edit] To be fair, this is not only, and even perhaps not so much, due to the static typing per se but also due to the mental discipline the particular programming language may require from the programmer even to write code that can be successfully compiled.
I'll bet my Python code has a similar probability of working correctly on the first run, conditioned on the complexity of the task.
'course, it probably wouldn't work fast, and it was 50/50 whether I'd introduced space leaks, but...
Indeed, difference in complexity is the key here.
Having developed professionally in both Python and Haskell, the probability for me was significantly higher in Haskell.
The author asserts that static typing allows the compiler to answer this question, but this only allows the compiler to spot type errors in advance. There are many other kinds of errors that are completely invisible to the compiler.
In a dynamically typed language, if I don't spot the error from reading the code, I must wait until runtime/testing to discover the error. This is also true for a statically typed language for every kind of error except type errors. Personally, type errors haven't been the kind of errors that haunt my dreams. I guess that's why I'm not enthusiastic about static typing.
Many people assume that 'types' are simply primitives like Int and String, and that a type checker just makes sure you don't pass an Int to a function expecting String. However, it is possible to express far more powerful statements about your data using a good type system.
For example, you can express the idea of non-emptiness of a container, as mentioned in the article. Then you know that, say, taking the max element of a non-empty container is guaranteed to give you an element, whereas with a possibly-empty container you might not have any element at all, causing a null, or exception, or at least requiring an Optional type.
You can express safety properties such as a sanitized string vs. unsanitized. You can have a Sanitized type that can only be created by calling a sanitize function - which carefully escapes/handles any invalid characters - and then functions that might, say, pass a value into an SQL instruction can be typed to only take Sanitized strings. Now the representation in memory of Strings and Sanitized strings is identical, but by using different types and a certain set of allowed functions on those types, you can encode the invariant that a string cannot be inserted into an SQL query until it has been sanitized. Now your type checker can catch SQL insertion vulnerabilities for you. How's that for a type error?
First, when most people talk about static typing, they're talking about the near-useless version -- just types like Int and String. I think we agree there, so I won't mention it further.
Second, a dynamically typed language like Python has more typing information than some folks first assume. Python's AttributeError is quite similar to a TypeError. In fact, with old-style classes (v2.1 and earlier), many errors that are now TypeErrors were AttributeErrors. Calling len() on an inappropriate object would raise "AttributeError: no __len__". In many cases where folks talk about wanting a static type system, they really just want interfaces.
The Sanitized string example is a good counter-point because the interface needs to be near-identical to a regular string. I'm not certain a more complex memory representation (caused by defining a different class) would cause noticeable inefficiency. We're probably not doing vectorized operations on strings.
This brings me to my third point, that Python 3 has a similar split between two types: bytes and str. The memory representation is slightly different, bytes vs unicode, but the interfaces are nearly identical. Two differences would be decode vs encode and that getting an element from bytes (annoyingly) gives an int. The distinction between the two types is enforced mostly inside builtin functions, implemented in C. This was a big deal, causing backwards incompatibility, many flamewars, and we're still resolving it, though I think it's clear to most people now that Python 3 is the future.
Is it possible that the Python 2/3 split could have been avoided if we had a static type system? Perhaps, if we had multiple dispatch, the function signatures could have remained the same, avoiding backwards incompatibility... I'm just speculating here. My guess is no, getting rigorous about unicode would cause incompatibility regardless of the type system. I'll get back to the main topic now.
> Now your type checker can catch SQL insertion vulnerabilities for you.
This sounds useful, but a good interface solves the problem just as well. I'm a Pythonista (if you haven't noticed), so my example is PEP 249 that specifies a DB API for all database wrapper implementers to follow. It states that it's the wrapper dev's responsibility to implement a sanitizing string interpolation for the cursor's execute method.
My conclusion is that designing a good interface is important whether you have dynamic or static typing. Static typing errs on the side of safety, dynamic typing errs on the side of flexibility. Both can mimic the other. Arguing that one is better is like saying linear regression is better/worse than k-nearest-neighbors.
I don't agree. Who is "most people"? Certainly not PL designers and not most of what I've seen here in HN. More importantly, it's also not what the article under discussion is saying, either.
> Static typing errs on the side of safety, dynamic typing errs on the side of flexibility. Both can mimic the other. Arguing that one is better is like saying linear regression is better/worse than k-nearest-neighbors.
In my experience, this isn't true. Modern statically typed languages have all the convenience of dynamically typed ones, such as REPLs and elegance, plus the safety of early warnings and the guidance that static types give you while writing your code (if you've ever written code like this, you'll know the feeling of working with building blocks that "fit" with each other). So you can have your cake and eat it, too.
Also in my experience, not having experience with these languages is what leads some people to think their type systems can only state trivial things such as "this is a String". They can do more. They can say things such as "this expression/function doesn't write to disk as a hidden side effect", which is useful!
Like a dynamic language with optional type hints? As I said, both techniques can mimic each other, with the corresponding tradeoffs. As you use more generics in a statically typed language, you're sacrificing safety. As you use more type hints in a dynamically typed language, you're increasing syntax clutter and decreasing flexibility.
Actually, in languages like Haskell, the more generic your type, the more "safe" you can expect it to be.
As an example, consider a function that gives you the first element of the tuple you pass to it.
The most generic type of this function is
However, it can also have the type Now, you can be sure of the behaviour of fst immediately by looking at its type, but that doesn't hold for fst1Pretty much the only definition of fst that the compiler will accept is
However, the compiler will accept all the following definitions of fst1Like the other commenter says, generics actually increase safety: there are fewer assumptions (and therefore, incorrect assumptions, aka bugs) you can make when your functions are generic. Also, modern statically typed languages do not increase clutter by much, and can be very elegant and brief.
The point of strongly-typed systems is that you can represent your constraints as types. This takes extra thinking and work, but gives you almost almost unlimited expressive power (ref: agda).
Simple example: meters and feet as different numerical types. When you multiply them, you get a silly unit (foot-meters) that doesn't fit with whatever you wanted (meters^2), and thusly fails compilation.
I also wonder when you would make that distinction in the lifecycle of your application. I suspect not until you first encounter the bug of accidentally mixing units. If so, we'd be solving the problem at the same time, just using different techniques.
https://docs.microsoft.com/en-us/dotnet/articles/fsharp/lang...
I think if you're used to a language like Java or C++ you may not see what types can really buy you, but that's because most languages have bad type systems.
Types are far more rigorous - you can ensure, with types, that your code behaves in a certain way given any input. Yes, this extends to "design" bugs ie: not just crashes - you can write a type that ensures that an API can only be used correctly, for example. You can encode logic like "Don't allow unauthenticated users to access this content" into your type system - and you no longer need tests.
Of course, type systems don't prevent you from writing tests. They actually make it easier, you can generate test cases based on types, as an example.
I also have never met someone who claimed to never need tests because of a powerful type system.
Of course having great test coverage helps alleviate this, but it's very rare where a large project has 100% test coverage.
I don't see how this can be true. Wouldn't there be less to specify if the programmer didn't have to specify types at all?
I think this is not generally true and not a point against static typing, but rather against statically typed languages with poor support for generics. Java stands out as the poorest implementation of generics I have ever seen.
> For instance, a generic serialization library can be written in a dynamic language, without anything fancy, but providing the same thing in a static language requires more machinery, and is sometimes more complicated to use.
As a counterexample, have a look at NimYAML (my work):
The examples there show how easy it is to provide the user with a generic interface for serialization in a statically typed language. The implementation does not differ much from what you'd do in a dynamic language: Provide a pair of serialization/deserialization handlers for each of [simple types (string, int, float, enums), array/sequence types, tuple/object/struct types, dict/map types, pointer/reference types].* Introduce new generic types in a new namespace. .NET did this with `System.Collections` and `System.Collections.Generic`.
* Default unspecified generics to `Object`, including usage of generic types in bytecode tagged with pre-1.5 versions.
This is a very important point that I've rarely seen talked about explicitly though I think all programmers develop a tacit sense of it.
On one hand, refactoring code written in a statically typed languages is less error-prone - But on the other hand, such refactorings tend to affect more code than those of dynamically-typed code.
If your business requirements change often and refactorings are common, it can be a pain to keep having to rethink your code structure.
Dynamically-typed code is often easier to extend and modify. I find that with statically typed code, if you start messing with a small part of your code, sometimes you have to rethink your entire class hierarchy. With dynamic languages, your code can handle quite a few changes before it gets to a point were you need to rethink the overall structure.
On one hand, refactoring code written a statically typed languages is less error-prone - But on the other hand, such refactorings tend to affect more code than those of dynamically-typed code.
Dynamically-typed code is often easier to extend and modify. I find that with statically typed code, if you start messing with your code, sometimes you have to rethink your entire class hierarchy. With dynamic languages, your code can handle quite a few changes before it gets to a point were you need to rethink the overall structure.
For example: a function that takes an integer between 1 and 100. That's a straightforward constraint! Elixir is an example of a language that lets you express some of those kinds of constraints, by using guard clauses. Along with its powerful pattern matching, you end up with very nice code.
What are other languages that are known for these kinds of constraints?
Consider the following: I have a "rating" type, which is a whole number from 0 to 5. If you try rendering a view with a number outside of that range, that's a bug! Which means you probably made a mistake somewhere.
I want software to help me catch bugs. If something can't be confirmed with a linter or compiler, getting a good error message during runtime is also fine. Once I've established that I expect some value, I don't want to write extra checks to confirm that my expectations are met.
Regarding your rating example, you could encode that restriction in a type (or class in OO) and have a guarantee that once you have an instance of that type you no longer need to check for it's validity. Rendering a Rating would never fail at runtime, you'd get a type error first.
For example,
Like puzzle pieces with shapes that we can observe fit together, we can think of types as specifying a grammar for programs that ‘make sense’.
All programming languages have a grammar for programs that make sense (it's specified by the parser). Algebraic data types give you a grammar for values that make sense. Separately, a type checker assigns types to expressions and checks that they're consistent. You can have algebraic data types without a type checker (e.g. Racket's 2htdp/abstraction), and you can have a type checker without algebraic data types.
You can also have constraints on values that are too complex to be easily checked at compile time (e.g. clojure.spec) or that can be checked at compile time but only incompletely (e.g. Erlang's -type and Dialyzer).
Anyway, I don't mean to be critical of the article: it does a good job covering the high-level tradeoffs. I just think people are too quick to generalize their experience with particular languages to entire classes of language features.