The Dangers of Dynamic Typing

It is often that I encounter those in my sphere of coworkers or friends who preach the ability of python or as of recent clojure. While I think the later is step in the right direction somewhat I still view dynamic typing to be fundementally at contrast with how I view software.

To me the difference in typing is two fold, it is seen in documentation and in the ability to read unfamiliar code. When you are the single author of a project then the difference in typing makes very little difference. When writing code it is easy to keep in your mind what types are what and how each function works. Likewise writing dynamic typed code is a bit easier on the hands, as you are just writing code with no worries about rigid definitions for how data must be formed. These two facts creates an illusion to a user, that there are only benefits to be had.

In reality however, we are not oftne dealing with our own code. Or at the very least our own code relies on the combined efforts of 10 different others and their own set of dependecies. If the recent npm culture has tought us anything it is that we live in an age where your deps most often time vastly outweigh your code in terms of weight. This means that the largest surface area of your code was not written by you yourself. This is where the stink starts to set in.

The relation of variable names, types and documentation are like the relation between the hostname and model of your computer. In your head the hostname is very descriptive of what computer you are talking about, you probably also know its purpose and what model it is. However, telling your friend just the hostname means next to nothing, there is no spot in their brain that relates it to the actual information about the machine. Now if you add the model information in with it, now your friend knows what it is capable of and what componets it has. Now for them to truly understand what it is, you should also describe what its function serves.

In dynamic typing you lose a crucial part of this balance, you lose the model information of the machine. Without a type, the user ends up either guessing, printing it out in a repl or relying on documentation to explain each of the fields well. In my experiance the last one is rarely the case so we are left with the first two. Guessing is a pointless waste of time and an insult to the programmers time. So we are left with printing it out in a repl. However this leaves the following issues:

What this does is create a headache, I feel I am often tasked with deciphering the cryptic notes left by the creator both in the code itself and the documentation. This had left me more often then not just going down the stack until I finally find the concrete information that I needed. In contrast this same concrete information is present as the first item in most static typed documentation(struct definitions). As a result when I am tasked with working on dynamic typed languages I can't shake this feeling that my time is being wasted for the benefit of saving time on behalf of the original creators.

On the issue of readability I want to present two examples, one of which is written in clojure and the other in Go. They both serve the purpose of working as a telegram bot api library. The following are the first example of usage of their code shown on their GitHub README.

Clojure:

(ns user
  (:require [morse.handlers :as h]
            [morse.api :as t]))

(def token "YOUR-BIG-SECRET")          

; This will define bot-api function, which later could be
; used to start your bot
(h/defhandler bot-api
  ; Each bot has to handle /start and /help commands.
  ; This could be done in form of a function:
  (h/command-fn "start" (fn [{{id :id :as chat} :chat}]
                          (println "Bot joined new chat: " chat)
                          (t/send-text token id "Welcome!")))

  ; You can use short syntax for same purposes
  ; Destructuring works same way as in function above
  (h/command "help" {{id :id :as chat} :chat}
    (println "Help was requested in " chat)
    (t/send-text token id "Help is on the way"))

  ; Handlers will be applied until there are any of those
  ; returns non-nil result processing update.

  ; Note that sending stuff to the user returns non-nil
  ; response from Telegram API.     

  ; So match-all catch-through case would look something like this:
  (h/message message (println "Intercepted message:" message)))

Go:

package main

import (
	"log"
	"time"

	tb "gopkg.in/tucnak/telebot.v2"
)

func main() {
	b, err := tb.NewBot(tb.Settings{
		// You can also set custom API URL.
		// If field is empty it equals to "https://api.telegram.org".
		URL: "http://195.129.111.17:8012",

		Token:  "TOKEN_HERE",
		Poller: &tb.LongPoller{Timeout: 10 * time.Second},
	})

	if err != nil {
		log.Fatal(err)
		return
	}

	b.Handle("/hello", func(m *tb.Message) {
		b.Send(m.Sender, "Hello World!")
	})

	b.Start()
}

First lets look at the clojure code. I can see how to set up handlers fairly easily, but what I cant see is what information those handler functions are given. I see them being passed an id and a chat variable. I can perhaps guess that id is some width of integer, but what about chat? What kind of information does that carry with it? If I check the cljdoc page, I am shown nothing about this, and instead simply presented with how to invoke the question ambigious data objects and all. Now to the authors credit, they then explain what this magic data blob is in the next paragraph by detailing that chat is being pulled out from a telegram 'message' type. The information for which is linked to the actual telegram documentation. Am I to assume that the members are named the same as they are on the telegram page? What about language conventions in regards to variable names? If the naming conventions did clash am I to infer how they were translated? So then how do I know for sure? Short of guessing I now have to download this library, pop the example in, and add some print statements to see the full values. Now at this step if I decide that this library is not for me, I have lost this time playing around with it for what should have been defined in the first place.

Now when I read the Go code here I immediately find two types that I will need to know. The type returned from tb.NewBot and the tb.Message type. I know that if I understand what these two types are, how to create them, and what functions I can use them with I can easily be on my way. Lucky for me these types are defined very explicitly on the generated GoDoc page. The docs provide me with a logical way of routing out how to get these types, what information they hold within themselves, what I can do with them. There is no guesswork needed, no download of the library required, no waste of my time. Static typing enforces structure on your programs not for your own good but for the good of others.

Now if we also consider that it is common that you dont have the luxary of a README explaining every bit of other peoples code you are using the difference becomes much more pronounced. With the go example I can do the following

And I have the definition of what the sturct is, and what functions act upon it, even without any documentation. If I were working on something similar in clojure I lose any context on what is happening unless I just mess around with printing the values or just guess.

It is for these reasons that I find working on dynamic languages anything short of masochism.