artisanal bytes

“Hand-crafted in San Francisco from locally sourced bits.”

The Difference Between Learning And Knowing

There is a myth in the software industry that anyone who knows a C-like language can easily pick up another C-like language in a week or so. That is simply not true. For one thing, everyone knows that a language is more than its syntax and that being productive requires learning the entire ecosystem around it – standard and de facto libraries, build tools, debuggers, etc.

More importantly, learning a language is only the first step on the way to actually knowing it. To help illustrate the difference, I figured I would make a list of all of the programming languages I have learned over the years and another list of the ones that I know.

Programming Languages I Have Learned1

  1. Logo
  2. HyperTalk
  3. BASIC
  4. Pascal
  5. C
  6. C++
  7. Prolog
  8. Assembly
  9. Java
  10. Perl
  11. Javascript
  12. Bash
  13. PHP
  14. Ruby
  15. Scala
  16. Haskell
  17. Clojure
  18. Elm

All of those languages I have used in some form or another. I have written at least one program with them. In the cases of the most recent ones, I have read books on them and sometimes even the language spec. I am by no means an expert with them; I don’t even have much experience with them. But I have learned them.

And now, here are the languages that I know.

Programming Languages That I Know

  1. Java

Really? Just one? Yeah. Just one.

What It Means To Know A Language

To me, truly knowing a language goes beyond just being able to open an editor and type fluently. It means that I understand how the language internals work; how the language interacts with its runtime environment; how it handles memory allocation and garbage collection; and what are its idioms and idiosyncrasies. I know the “best practices” and when to break them, how to efficiently debug a program in that language, and where the pitfalls hide.

Truth be told, I’ve been using Javascript exclusively for the last two years, and I certainly know it a whole lot better than I did two years ago, but I’ve not had to dig deep into the internals to optimize it; I’ve never written serious streaming code; I still don’t have a complete handle on promises; and, well, the ecosystem is so diverse that I haven’t settled on my own choice of best practices and idioms yet. So, while I know it very well, I certainly would not claim to know it the same way I do Java… or at least the same way I knew Java two years ago, when I last used it.

It takes a lot of time working with a language in real world scenarios to really know it. And that is fine. As Peter Norvig pointed out in his essay “Teach Yourself Programming in Ten Years,”

The key is deliberate practice: not just doing it again and again, but challenging yourself with a task that is just beyond your current ability, trying it, analyzing your performance while and after doing it, and correcting any mistakes. Then repeat. And repeat again. There appear to be no real shortcuts.

I worked with Java for roughly eighteen years. I feel ok not knowing Javascript as well after only two years of dedicated effort with it.

There appear to be no real shortcuts. There’s a life lesson for us.

Why Learn So Many Languages Then?

In a phrase – diversity of perspective.

The most important aspect of learning different languages is to open your mind to new ways of looking at the same problem. When you solve the same problem over and over again, such as writing a web app, in different languages, you learn the cost and benefits of different tools and methods – where does static typing help and where does it get in the way, for instance. And if you approach the language learning process with an open mind, you can bring back some of those benefits to your day-to-day language, even if it does not explicitly support the features.

A language that doesn’t affect the way you think about programming, is not worth knowing.

Alan Perlis

When I studied Scala, I learned about co- and contravariance. These concepts are obfuscated in the generics system in Java, so while I know how to use generics, I did not fully understand what they meant until learning Scala. Learning Scala helped me better understand the design trade offs I needed to make when I was writing Java classes.

Another gem that I brought over from Scala into Javascript (thanks to ES6) is the use of let and const, a direct parallel to Scala’s var and val. Even though the Javascript runtime does not necessarily enforce the meaning of let and const, I use them as an indicator of intention in my code so that when others or I read it later, we can easily scan and know whether a variable is meant to be changed or is expected to always be the same.2

The Practice of Learning

Another benefit of learning new languages is that it is a way to practice learning. Contrary to the myth mentioned at the start, learning a new language is not an easy week-long exercise, unless all you want to know is the syntax (and even then, with some languages, a week is not enough). It takes practice to be able to pick up a new language quickly. So, you have to practice how to learn. Do you read the language spec? Do you boot up a REPL and start playing? Do you have a project you implement repeatedly to directly compare multiple languages? Exercising your brain to learn languages will make it easier to learn more languages in the future, and in our fast-paced world, that will always be beneficial.

Furthermore, some of the concepts in some languages are really hard to understand, so the learning part takes some effort. I’m reminded of the several new types of types3 that I learned in Scala and the concept of monads I was introduced to by Haskell. Fortunately, many of the language design concepts are repeated over and over again, so when you learn several languages, you can draw connections between them and use what you learned in one to more quickly learn another. After learning about algebraic data types in Haskell, I instantly knew how to use them when I encountered them in Elm. I was also able to draw a quick parallel between monads in Haskell and ports in Elm, even though they are not the same thing. Learning the first made understanding the second easier.

Learning New Things

Some languages teach you entirely new concepts in computer science. Before learning Clojure, I did not know about persistent data structures at all. Rich Hickey, the author of Clojure, didn’t invent them, but he used them in order to efficiently uphold a fundamental principle of his language, that all data structures be immutable by default. If I had not bothered to learn Clojure, I may not have ever bothered to learn how persistent data structures work.

I had a similar experience with concurrent sequential processes. They are baked into Go, and while I haven’t yet started learning Go (aside from a quick read through of the language introduction), CSPs were available in a library in Clojure and started migrating into the Javascript world. So, as I deepened my learning of Javascript, I dove into how concurrent sequential processes would work in Javascript and where they could help out.

My third example comes from the world of Scala. There was a fair amount of discussion and research into software transactional memory when I was studying Scala, and a couple years later when I learned Clojure, I interacted with its refs, along with non-STM atoms and agents, primitives meant to help with concurrent data access that are built on STM concepts.

These are three very powerful constructs in the modern world of computing that I may not have learned if I had not been learning new programming languages.

What’s It All For?

Behind all of this learning is my desire to continuously improve as an engineer. Learning new languages is part of the process, but what stands out most to me is that learning the fundamentals behind the languages is what really matters. Knowing how to type Scala code does not make me a better engineer. Understanding that there are trade offs that come from how permissive your data structure is with what types of object it will accept does. Remembering how to escape my macros when writing Clojure means nothing. But applying the concept of treating data as values can improve how I write Javascript on a daily basis. Being forced by Haskell to separate my pure logic from the dirty world of side effects means that I can design my Javascript UI code with a similar separation and benefit from testability, maintainability, and understandability.

So, learn new languages. Not to pad your resume, but to pad your brain.

  1. For the purposes of this exploration, I am not counting domain-specific languages like SQL, HTML, or CSS, which I do happen to have both learned and know. I’m also not counting “regular expressions” which are essentially a language of their own. Furthermore, I’m not counting languages that I “learned” just enough of to accomplish a very specific task, such as Python, PostScript, and TeX. 

  2. I had been using a similar technique in Java for years thanks to the final keyword. I littered my code with it – final parameters, final class declarations, final variable declarations. But it signaled intention, and thanks to a static environment, it was enforced by the compiler and runtime. 

  3. Scala includes the notion of existential types, which is a mechanism to indicate that there is a type involved in the expression but what type it is doesn’t actually matter right now. And I thought only humans had issues understanding their true meaning in life.