Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

A couple years ago for a programming languages course, we wrote a bytecode compiler and interpreter for a JavaScript-like language we were using in the class (objects, prototype-based inheritance, higher-order functions, etc), and we initially started building it in Go, but the biggest thing that made us switch to C++ at that time was the fact that Go didn't have a straightforward union type.

It looks like this interpreter is using tagged unions for values, and using the empty interface to emulate a union type. I seem to remember that we may have read something at the time that recommended using the empty interface instead of unions, though I don't remember for sure. Nice to see some interpretation efforts finally being realized in Go!



Sum Types in Go - http://www.jerf.org/iri/post/2917

It's less convenient than writing a compiler in Haskell, but then, what isn't? It does give you reasonable type safety, though. (Again, don't say that where a Haskell programmer is listening, but it's at least decent.)


Thanks. That was really interesting.


Excuse the silly question, but why was a union type so important?


Interpreters typically use polymorphic locations to hold values for the interpreted language. Technically almost any type of polymorphism is enough, but the fewer indirections you can get away with, the faster you can make a simple interpreter. So that would usually mean a sum type in a functional programming language with algebraic data types, or a simple object in an OO language, or a tagged union in C and Pascal.

But for better performance, approaches like tagged pointers (often used in Lisp implementations) are useful, as it eliminates a level of indirection for most integer operations. If you can afford to use 64 bits for your values, you might consider using doubles everywhere, with invalid exponents for stuffing a shortened pointer inside the float (I believe luajit uses, or used to use, this technique; it would probably be a good match for JS as well, as JS doesn't have integers).


I was wondering myself why you needed a union type.

Is it because JS has dynamic types ? var x = 5; x = "John Doe";


It allows you to create a single, compact structure that can then be utilized in a number of ways without having to re-cast it, replace it, or otherwise reallocate it.

For example, you can have a union between a 64-bit pointer and a 32-bit type identifier plus 32-bit value. This means you can store 32-bit integers in the same space as a pointer.


The question is what about a JS interpreter makes this necessary.


As I answered above to another commenter with a similar question, JavaScript is a dynamically typed language and representing dynamic values in a statically typed language requires a bit of thinking, and unions are a common way of doing this.


The alternative is a data structure which has N-1 empty, yet allocated, slots (N is the number of possible types.) instead you can have one field indicating the type of the variable and then your code can switch which field it accedes based off that indication. Total size is one int plus size of largest type represented.


You could potentially also have an interface that defined methods for all the basic types `interface { AsString() (string, error), etc. }` and then each basic type implements this interface and returns an error if it can't/shouldn't be represented as the requested type. A type-switch could give you the same info you get from the tag and you don't pay for type-assertions


Yes. JavaScript has dynamic types, and representing these in a statically typed language requires some finagling.


Casting to/from empty interfaces is kind of doing the same thing for void* pointers in C, right?


No, for once, Go doesn't have casting, it has type conversions, but they are type safe. With void * you can do anything, with interface{} you can only use the dynamic types inside the interface.

That being said, using interface{} for unions is extremely unfortunate. The bright side is that after 4 years of using Go almost exclusively, I only had to abuse interface{} only once, with compiler parsers (like the parent says). Every other time there was a better design which did not require using interface{}.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: