Thursday, March 30, 2017

What is wrong with static typing in JavaScript and how clojure.spec solves the problem (Part 1)

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! :)

Friday, March 17, 2017

Sorting maps in Clojure

Problem 

If you like to keep data in maps in ClojureScript to be able to access it fast, but also need sorting, maybe you should read this.

 Cause

 Let's say you have a map like:

 (def a {:0 0, :1 1, :2 2, :3 3, :4 4, :5 5, :6 6, :7 7}) 
 (vals a) would return: (0 1 2 3 4 5 6 7) 

 But what about

 (def a {:0 0, :1 1, :2 2, :3 3, :4 4, :5 5, :6 6, :7 7, :8 8}) 

 where

 (vals b) returns: (6 7 4 5 1 0 3 2 8) 

 The trick is how data is represented internally. If the number of pairs is less then 8 then (type a) is a clojure.lang.PersistentArrayMap but (type b) is a clojure.lang.PersistentHashMap which is optimized for access, but loses order as a compromise.

 If we generate our maps using a function:

 (defn gen [x] (doall (map (fn [x] [(keyword (str x)) x]) (range x)))) try: 

 (->> (gen 8) 
         (into {}) 
          type ) 

 (->> (gen 9) 
         (into {}) 
          type ) 

and you'll see for yourself.