Hacker News new | ask | show | jobs
by nerdponx 3199 days ago
One of the big perks of Julia comes from its built-in multiple dispatch. Overloading is one thing, but full-blown multiple dispatch is really powerful in a math context, where the concept of "multiplication" is entirely dependent on the types of things you are multiplying.

In Python (despite there being an excellent `multipledispatch` module) this is mostly just handled by aggressive duck typing ("if it has a .foo method, it's good enough"). In R it's handled with S4 classes, which are cool and kind of CLOS-like but are even slower than single dispatch.

So I guess my question is: why do you need generics when you have interfaces? These other (admittedly dynamically typed) languages make do without.

2 comments

For numerical computing, rather than dynamic multiple dispatch, what is actually desirable is a type system that can figure out statically the kinds of result produced by multiplying different kinds of arguments.
"So I guess my question is: why do you need generics when you have interfaces? These other (admittedly dynamically typed) languages make do without."

Going backwards, as you allude to, dynamic languages fulfill the use cases for generics, as long as you don't care about type safety, which is a thing that is true for the whole language anyhow so it's not much to give up.

For Go, the main problem is that when you're trying to be mathematical, with interfaces you get the worst of both the static and the dynamic worlds. You might like to define an interface that lets you add two vectors, right?

    type Vector interface {
        Components() []float64
    }


    type Add interface {
        Add(Vector) Vector
    }
which might let you implement an Add method on something that is a Vector as well, but you don't get a satisfactory result from either perspective. From the static perspective you can not, using interfaces, guarantee that someone doesn't add a Vector3 to a Vector2, meaning you must either panic at run time or have Add potentially return an error (that will generally not be necessary to check if used correctly, which is not a pleasant error to work with). From the dynamic perspective, you have to remember that what comes out the other end of that operation is always an Add interface value, not a concrete type, so if you have a Vector2 and .Add(Vector2) to it, you don't get a concrete Vector2, you get a value of type "interface Add", which you have to manually cast back to a Vector2 if you want to do anything more than just keep adding to it.

You can make Vector2 have a distinct .Add(Vector2) method which does return a Vector2, but then if you also have a "func (v Vector3) Add(Vector3) Vector3" function, there is no way to declare an interface that both of those methods can meet, so you can not write any dimensionally-oblivious code that uses generic vector adding.

In "normal software engineering", Go's interface limitations are often not so bad, certainly not as bad as is often portrayed on HN. However, when you try to create a strongly-type numeric system (and you want it to be strongly-typed because that's also how you get good performance), Go's interface mechanism is basically worthless.

> and you want it to be strongly-typed because that's also how you get good performance

What you get performance from is the absence of dynamic checks, not the presence of static ones. Of course, in the absence of dynamic checks, you want static ones for your sanity's sake - but not for performance's sake!

I was speaking in the context of Go. In general, this is the sort of code that JITs are so good at handling that they tend to fool people into thinking they are miracle workers everywhere else where the JIT expense isn't being amortized across million-row matrix multiplications. But Go doesn't have a JIT, and its performance is good enough that I don't expect one to emerge any time soon. (Languages running 50x slower than C have a lot more pressure to try to solve that problem with a JIT than languages that are only 2-3x slower than C.)
> But Go doesn't have a JIT, and its performance is good enough that I don't expect one to emerge any time soon.

that's true. that is... until a (real) Go interpreter shows up. something that's bound to happen when Go will be used for (data) exploratory work.

I poked around with writing a Go interpreter a while back. There are a number of issues that make it practically infeasible. You can get some hacked-up stuff off of GitHub, but those hacked up things are pretty much the best you can do right now.

But as per my other thread in this thread, if the scientific community becomes big enough I wouldn't be surprised they fork Go entirely, at which point that opens up a lot more options.

as I am working on https://github.com/go-interpreter/wagon, I'd be very interested in these issues you're talking about.
Relying on a JIT to get good performance is arguably a bad thing anyway, since JIT compilation makes performance harder to predict. Of course, in the end you need to measure what you have, but you should also be able to make educated guesses about performance when you don't have something to measure, e.g., when you need to select between several alternative designs, and implementing all of them would be prohibitively expensive.