Two modern languages, two ends of spectrum
I talked to a lot of people at JuliaCon and was surprised to find that
almost no one had used the Go programming language for any serious work. Julia was invented in 2012
so it no surprise that everyone had programming experience in another language. Most people knew at
least two of
C, C++, Fortran, or Python the languages of scientific computing. And a lot of people
lisp, scala, or haskell. It is also obvious in a community focused on a novel programming
language that people would know multiple languages. But nobody I talked to had written a substantial
Golang project. Why is this?
If you ask me, the simplest explanation is the fact that Go and Julia are both new languages and at totally opposite extremes of the design space. Julia has a rich type system designed for mathematics, which is the subject of a great dissertation by Jeff Bezanson, innovative multiple dispatch, and syntax dreamed up by asking “What if MATLAB were a LISP?” On the other hand, Go has no inheritance, no generics, and no metaprogramming. It is the “simplest thing we could use instead of C.” Unless there is someone at GopherCon extolling the virtues of Julia, I might be the only person in love with both languages.
Of course, I love Go and Julia for totally different reasons and purposes. I developed most of my dissertation in Julia, and my road to Julia was fairly typical: C until I need to do some more math, then SciPy until I hit performance bottlenecks, then Julia ever since. But I have also used Go for some serious work. I wrote some parallel streaming data structures in Go including some sketching software, and distributed data processing system at Ionic Security. It gathered data from a distributed system in Go and then served up some derived data to an analysis service written in Julia. I have also written some tools for interacting SQL databases over HTTP.
I have used C, Python, Julia, Java, Bash, and Go for writing web code and I will certify that without a doubt the easiest and most pleasant experience is using Go. The standard library for web programming is excellent and the error handling model is perfect for writing networking code where failure is the norm not the exception.
Benefits of Go
Everyone can learn from Go’s success. Even though Go is Luddite in terms of PL features, it is extremely effective and wonderful tool Why Everyone Hates Go.
The primary reasons for Go’s success are:
- Tooling, there is only one way to build, run and deploy Go programs and it is easy
- Static Checking, there is great enforcement of static guarantees, not as good as Rust but easy to use.
- Simplicity, there are no template metaprogramming errors and everyone writes in imperative style.
As my friend and 50X programmer Rob McColl says:
In C++ everyone uses templates and method overloading to invent their own little DSL, which are all incompatible. You can’t do that in Go, so you have to stick to the same basic types. This means everyone builds around the same basic structures and code works together.
Go and Julia similarities
While there are some extremely significant differences between these languages, there are also some big similarities. Both languages have a type system and runtime with big escape valves on their static nature, allow deferred interface definition, and aim for simplicity and orthogonality. Julia is a dynamically compiled language where each function should have a static type that can be inferred at run time. Julia relies on this to compile fast code specialized for the types at hand 1 2. But Julia has this handy escape hatch when you fail to infer all the types ahead of time. If you can’t infer the types just box everything and do it live! It will be slow, with run-time type checks like Python, but it will work and people don’t have to worry about the program failing to compile or crashing just because the types can’t be worked out ahead of time. Go has a similar feature. All structs are known ahead of time and if you have statically typed code the compiler can skip a lot of run time checks about types. If you don’t know the types ahead of time, which is often a necessity in IO or library code, then the runtime will help you out with interfaces, typeswitches, and reflection. You can use some features of dynamic typing without throwing the statically checked baby out with the runtime type resolution bathwater. These escape values have completely different implementations, but the effects are similar. You get a programming language as fast as C in performance critical sections, and as easy to program as python when you need the flexibility.
Interface design is the hardest part of building big programs written by multiple people. If you just need to implement an algorithm to show that you can, then you don’t need to design good interfaces. But if you want to build libraries and ecosystems that other programmers want to use, then you need to design good interfaces. Unfortunately, many systems that require explicit and ahead of time definition of interfaces make big mess of everything, so both Go and Julia handle interfaces in a lazy way. In Go, you can always add methods to a struct, which means that you can import a package that provides a struct and a different package that defines an interface and write the methods that make the struct satisfy the interface. You don’t need to Go into the source code of the first package and modify the class, or make a subclass that lives in a third package. This allows for extensible interface design and easy interop within the ecosystem of packages. Julia handles this in a similar way with multiple dispatch. In Julia there are no methods, so any function can be extended with new types of arguments in any module. These leads to a very compact namespace where most packages use names shared around the ecosystem with similar names. The only function names you introduce are those unique to the problem at hand. Everyone shares the names in Base. This sense that all of the code is open to extension is a great thing #12064. In Go you get a compiler that says, “if it Duck()s like a Ducker then its a Ducker to me”, and in Julia you get a method dispatch system that says, “if I can’t find a method on the Duck function of the appropriate type, then I guess we can’t Duck here.” In both cases you can make Ducks out of anything you want.
Julia and Go share some sense for the orthogonality of features. Instead of having lots of features
that do similar things, you want to have the minimum set of features that let you solve all the
problems — with one obvious solution. The types and inheritance have similar properties in Julia
and Go. While the type systems in general couldn’t be more different, they share one thing in
common. You can’t inherit from concrete types, and you can’t instantiate abstract types. In Julia
this is due to the fact that you can inherit from
abstract types but not from
struct types, and
in Go you can’t create
types that are modifications of other
types and you can’t instantiate an
interface. The structs are defined by their memory layout and the interfaces (abstract types) are
defined by their behavior. This separation prevents a lot of the problems in object oriented design
where people need getters and setters or mess with parts of their data structure defined in a file
from some other open source package through some Abstract Base Class. By encouraging this kind of
orthogonality both languages achieve a simplicity that makes programming easier.
What can Julia learn from Go
Error Handling: Go’s approach to errors is great, errors are not exceptional — they are expected.
There is already a great simple package for this in Julia ResultTypes by Eric Davies (@iamed2). Some implementation of the
deferkeyword would be great in Julia, it can probably be done with a macro.
CSP, Goroutines, and Channels: Go’s concurrency primitives are the killer feature and this has already been integrated into the Julia standard library in the form of a
Tooling: gofmt, gofix, govet are all great tools for encouraging good programming behavior. By standardizing good behavior into code and automating compliance, conformance is easy and we all benefit from code that looks right, uses up to date APIs, and is free of race conditions and machine detectable bugs. Julia has a linter, but application of it is far from universal.
What Go can learn from Julia
- Taking Math Seriously: Go hasn’t solved rounding while Julia has solved the fundamental problem of vectors Taking Vector Transposes Seriously.
- Generic Programming and Metaprogramming: Julia has it and Go just doesn’t.
- REPL and Notebooks GopherNotes could be a really
good notebook interface but the Julia Repl and notebook is so great.
You might think that Julia and Go are too far apart in the programming design space to learn anything from each other; one camp thinking that the other are PL philistines, while the other says “humbug, we don’t need your fancy type systems and metaprogramming”. But if we see how these languages are designed to fit their niches, we can break out of the niche and leverage the knowledge contained in the other language. A lot of the success in Julia came from skipping all the bad ideas in Matlab, R, and Python. A lot of the success in Go comes from avoiding the bad ideas in C++ and Java. By looking at these two programming languages we can realize the true vision of one programming language that is the best for all tasks.
Image Credit: Renee French and Julia Developers.