Scala Saturday: Static vs dynamic typing
Type inference makes static typing easy in Scala
Scala is statically typed. “Boo, types!” you might be thinking. “So restricting and tedious! Jeeves, drive me back to my scripting language!” Before you write off static types completely, let me share a bit about my background with types.
A Tale of Two Types
I wasn’t always Mr. Monad. I didn’t always “push my errors into the compiler”. Nope, I would just let it fly. “Ship it and see if it survives!” I’d write and run. I’d write some more and then I’d run again. But little did I know what I was running from… was types.
“Static typing is for academics,” we’d trumpet. “We have no time for types. We’re too busy being more productive by not writing them!” Sound familiar? At some point a dichotomy arises of “static languages are boring because you have to specify the types and dynamic languages are funner because you never have to specify any”.
But the code eventually catches up to you. Things get more complex. The code base grows, teammates change, and you can’t hold it all in your head anymore. “Was that an array or an object?” What was once an integer is now a string, or sometimes nothing at all. Have you ever woken up in a cold sweat thinking, “Was that a real class? Or just a hash dictionary? Did I spell those keys correctly?”
Living a professional life of second-guessing my own work had taken its toll. I was looking for something else, something with consistency.
Here comes the big bad, static typing in Scala. Contrast with Java and C#, the types go on the right of the identifier.
Every value, parameter, and method has a type associated with it known at compile time. Without running the program, the compiler can tell if your program is correct based on the types that flow through it. This handles basic, logical mistakes like “you gave me a
String but I needed an
One of the chief complaints with static typing is the need to explicitly specify types for everything. Right away, we can see a bit of redundancy. The type annotation on the left is often the same as the value being constructed on the right. It becomes even more cumbersome when using a type like
Map which takes two type parameters. Twice the duplication and twice the friction!
Type inference to the rescue
Many languages nowadays feature some form of type inference where the compiler can guess your types at compile time. Often, the type of your value will be based on the expression that it is being assigned. In the case below, we can tell that
age will be of type
13 is an
Int literal. Likewise, we can infer that the type of
pet will be
Dog since we are constructing one on the right side.
Scala can even infer the two type parameters of
Map. Recall that
Map is a type that takes two additional types within it, one for keys and one for values. In this case, the full type of
Map[String, Int]. In this case as well, the Scala compiler is smart enough to infer the kind of
Map we are building based on the literals passed into its constructor. Likewise, when we want to case/match against the individual elements in
dictionary, we can relax the annotations there because the compiler can infer from context.
describePets method has its type inferred. In Scala, the last expression in a block will be that block’s return value and type. Since
descriptions is a
Seq[String] and is the last line of the method, the entire method
describePets must also return
Having type inference of this level of power is extremely convenient. We can depend on the compiler to help us during development. Firstly, we don’t need to annotate everything. If the type of some domain concept changes, that new type will propagate to all of the areas where its type is left to be inferred. The compiler will automatically infer that new type without us having to change any annotations. Secondly, if we happen to miss any spots or leave the code in an illogical state, the code wouldn’t compile, preventing any surprising runtime errors.
Let’s revisit that dichotomy from earlier. Is it still true that all languages with static typing come with a high usability cost?
Newer languages have increasingly powerful forms of type inference, relieving you of the burden of annotations while enabling you to reap the benefits of compile time safety.
We can take a look at dynamic types through the lens of a static type system. We can describe a dynamic program as having all values, variables, and functions belonging to one type whose behavior isn’t resolved until runtime. In Scala, we call this type
Any. Dynamically typed code can take in
Any type as an input and return
Any type as an output. This amount of power could be a blessing or a curse, but more often curse than not.
increment we’re always expecting an
Int and returning an
Int, but our annotations are under-specified. In theory
increment could take anything and return anything. Is that true in practice? Is that expectation ever violated? What would our code do if that wasn’t the case? Why leave any of these suspicions to chance? Why not say this up front and have the spec match our expectations? It’s this constant stream of doubt that has made me shy away from dynamic types in favor of typed code that has much more consistent, targeted behavior.
There may be some cases where you do want to match on the type of some input. But these cases are much more limited in scope than the ability to match on anything at all. There is almost always a way to accomplish what you want using a static workflow and types. For example, if you were dead set on having a function that could take in or return either a
String or an
Int but no other types, there’s actually another mechanism you could use called
Either. Your output type would be
Either[String, Int]. With this container type, we’ve narrowed the scope down from
Any to two specific types can be analyzed during compile type, free of any ambiguities or problems at runtime.
Having been on both sides of the static/dynamic fence and building a career on both, I’m highly skeptical of ever returning to dynamically typed languages. Newer languages and stronger type inference systems have made the path to static typing much more palatable and fun.
Special shout out to Ryan LeCompte who detailed a similar experience with Scala in his 2013 Scala Days presentation Confessions of a Ruby Developer Whose Heart Was Stolen By Scala.
The debate rages on but in a statically typed way. Have you worked with both type systems before? Was there something I missed? Or something you disagreed with? Comment below!
Until next time, happy hacking and see you on the next Scala Saturday!