The problem
The main problem with dynamic typing seems to be fear that the wrong type of data will end up in the wrong place (function input for instance).
However it turns out static typing is pretty useless.
Example 1: Simple types Age
Let's say you have to record an age for a person in a variable, or have it as a parameter in a function.
You would do something like:
int age = 25;
or
function something(int age...)
Someone pretended even that static typing shows intent. Now the only thing that is in here, is that it will be an int. There is nothing protecting the age from being either negative (-2) or too big (4000, it might work if you're talking about the age of the pyramids). So it is not intent, it is just int, not further protection, so pretty much useless.
Example 2: Composed types: Person
Let's say we get through a web call a json like:
{
"id":6,
"name":"Dan",
"age":28
}
Usually people would create a class
class Person
{
int id;
string name;
int age;
}
So we have the same problems, for instance name might be null, or age might be negative.
Then in modern apps, you get json and you send json, so you need to be able to serialize and deserialize to json this class. What happens if one of the parameters is not comform or missing?
Example 3: Hierarchies
What if you have:
{
"id": 6,
"name": "Dan",
"age": 28,
"children": [{
"id": 7,
"name": "Alex",
"age": 5
}
]
}
When the first object is a parent, in a school and he must have at least one child? You can enforce the relationship by writing a function and in the constructor, but then you also need to change the serialization/deserialization from json to enforce the rules, and of course you will write more code and you will forget to check it once, and there will be a bug.
And the most common problem of our times. The json is like:
{
"id": 11,
"name": "Maria",
"age": 95,
"children": [{
"id": 5,
"name": "Elena",
"age": 28,
"children": [{
"id": 6,
"name": "Dan",
"age": 60,
"children": [{
"id": 7,
"name": "Alex",
"age": 5
}],
{
"id": 9,
"name": "Alina",
"age": 32,
"children": [{
"id": 121,
"name": "Luiza",
"age": 0
}]
}
}],
{
"id": 23,
"name": "Petru",
"age": 70,
"children": [{
"id": 4,
"name": "Adrian",
"children": [{
"id": 45,
"name": "Denis",
"age": 12
}],
]
}]
}]
}
You have a single error but where? (Maria / Petru / Adrian - missing "age"). It is not only hard to validate it but it is hard to show explicitly where the error occurred.
Example 5: Functions
Let's try to find a string in another string. A function would be like:
int indexOf(string search, string what) ...
Which tells you that you will get an int, and you can pass two strings. First what if the strings are null? What if both strings are empty "", "". What if the result for "ab", "b" is 1248764 or -12. According to the function definition it is an int, and should be valid.
Example 6. Unit testing
To ensure the function above is well specified, we also use unit testing. Problem 1: unit testing doesn't care if it is static or dynamic typing. Problem 2 is very unit testing specific: Having enough tests, maintaining them when the function changes (like adding a new parameter), not enough testing, or too optimistic testing.
The solution proposed by Clojure.spec will be shown in part 2. And it is pretty cool! :)