DEV Community

Phoebe Goldman
Phoebe Goldman

Posted on

why i (sometimes) prefer dynamic typing

this is a response to Alexis King's blog post "No, dynamic type systems are not inherently more open."

i've written a fair amount of Rust in my day. (starting my post like this is my way of defending myself against angry static typers). i know the value of a strong type system which proves the assumptions you make in your code, and i know that static typing is an elegant way to annotate a program with compile-time invariants. i know the joy of catching bugs in the compiler and fixing them before they ever have to run, of ci time saved by catching a mistake in a local compile rather than a remote test suite, and i know how much a consistent and versatile type language improves generated documentation and library interfaces. but sometimes, dynamic typing is still the right choice, and y'all need to shut up and get over it.

i also don't want this to seem like an ad-hominem attack. i'm responding to this blog post because i think Alexis King did a good job of concisely
articulating some arguments that i've seen in weaker and less well-considered forms elsewhere, and i think she gives me a good opportunity to explain why i feel differently.

as their first argument, the author of that blog post presents an elegant and intuitive Javascript program which concisely does the correct thing (namely, ignoring events of unknown event_type when it falls through the switch):

const handleEvent = ({ event_type, data }) => {
  switch (event_type) {
    case 'login':
      /* ... */
      break
    case 'signup':
      sendEmail(data.user.email, `Welcome to Blockchain Emporium, ${data.user.name}!`)
      break
  }
}

here's King's strongly-typed Haskell alternative:

data Event = Login LoginPayload | Signup SignupPayload
data LoginPayload = LoginPayload { userId :: Int }
data SignupPayload = SignupPayload
  { userId :: Int
  , userName :: Text
  , userEmail :: Text }

instance FromJSON Event where
  parseJSON = withObject "Event" \obj -> do
    eventType <- obj .: "event_type"
    case eventType of
      "login" -> Login <$> (obj .: "data")
      "signup" -> Signup <$> (obj .: "signup")
      _ -> fail $ "unknown event_type: " <> eventType

instance FromJSON LoginPayload where { ... }
instance FromJSON SignupPayload where { ... }

handleEvent :: JSON.Value -> IO ()
handleEvent payload = case fromJSON payload of
  Success (Login LoginPayload { userId }) -> {- ... -}
  Success (Signup SignupPayload { userName, userEmail }) ->
    sendEmail userEmail $ "Welcome to Blockchain Emporium, " <> userName <> "!"
  Error message -> fail $ "could not parse event: " <> message

which this is a considerably longer, but still quite intuitive, block of
Haskell which does the wrong thing (namely, error when a message's event_type falls through the case statement). this version is more lines of code and makes more information explicit which would be assumed in the Javascript version. King considers the latter an advantage, and describes how it's possible, at the cost of some additional thought, to do the wrong thing in Javascript or the right thing in Haskell.

in case i haven't been clear enough, i think this is a pretty bad
argument. it's trivially true that it's possible to translate a program from any one Turing-complete language to another, which is pretty much the only real point unambiguously in Haskell's favor in this case. King touts it as a feature that Haskell requires the programmer to compute the most general types of their terms in their heads, but i see that as a huge point in Javascript's favor. in programs that perform trivial transformations on complex objects, i find that this effort is rarely worthwhile. King, though, is happy to dismiss the overhead during an apparent response to a Reddit comment in favor of dynamic typing,

It’s definitely more boilerplate, but some extra overhead for type definitions is to be expected (and is greatly exaggerated in such tiny examples), and the arguments we’re discussing aren’t about boilerplate, anyway.

i contest the claim, actually, that this is a case where the overhead is
exaggerated by the size of the example, as i have frequently written and read interchangeable or equivalent Javascript in the wild. i believe it's useful, valuable and a huge strength of Javascript that it's easier to conceptualize and write this exact function and that it takes fewer lines of code. this may not have been the subject of the comment in question, but i think it's actually worth discussing, whereas i frankly think the actual comment is inane and could have been chosen as a strawman (though it seems unlikely to me that it was). the actual point of dynamic typing is that it makes doing "the obvious thing" easier to think of, easier to write and easier to read, provided you and the language agree what is "the obvious thing". explicit static typing and type-based formalism are valuable in complex problem spaces, but actually, not all problem spaces are that complex. sometimes "the obvious thing" really is obvious, and i just want to write it and move on with my life.

King writes somewhere just past the introduction of her premise,

...programs are not capable of processing data of a truly unknown shape regardless of typing discipline, and static type systems only make already-present assumptions explicit.

if this is a surprise to you (which, sadly, it may be to some of my coworkers and classmates...), you need to deepen or reconsider your understanding of dynamic type systems. this is true, and you should know it. but i fear that, all too frequently, newer programmers who have just discovered this fact lose sight of the fact that, in simpler problem spaces, leaving assumptions implicit can save valuable mental overhead, which means work, which means time, which means money.

also, in cases where the assumptions are obvious but programmatically complex, it may not be worth the time to design a type system which accurately encodes the language's behavior. King refuses to refute this point; she responds to a HackerNews comment which is short and somewhat ambiguous, and interprets it in a way that leaves me unsatisfied. the comment reads:

What would be the type signature of, say, Python’s ~pickle.load()~?
from [https://news.ycombinator.com/item?id=21479933]

King responds with (a longer and clearer version of) the obvious answer, which is that pickle.load() (pickle is apparently a library that serializes and deserializes arbitrary Python objects) returns an Any, and that you'll have to do type-checks before you use it. while this is an accurate type, it's not particularly useful. in fact, i'll make the bold claim that it's never useful to describe a value as an Any, because that just means you know nothing about it. the dynamically-typed code is just a shorthand that inserts implicit type-assertions wherever necessary to make the program well-typed, and frankly, i prefer not to write them myself. it would be useful if a type system could predict and verify the return-type of pickle.load(file) by combining its argument with information about when the program calls pickle.dump(T, dump), but i think in this case, building the type-checker would be more trouble than stomping all the bugs it would catch.

Discussion (0)