Hacker News new | past | comments | ask | show | jobs | submit login

You’re always going to be on one side of the expression problem [0]: either your operations live with your datatype (classes), which makes it inconvenient to add a new operation, or they live somewhere else (“functional modules”), which makes it inconvenient to add a new case to the datatype. This choice is probably informed by your expectations of how often you intend to do either of those things, but there’s going to be inconvenience regardless.

The big benefit of objects, and therefore classes (or something like them), is polymorphism via dynamic dispatch; it seems a shame to throw the baby out with the bathwater because you don’t like inheritance or mutable state, both of which are independent of the choice to use classes.

[0] https://en.wikipedia.org/wiki/Expression_problem




I’m not sure dynamic dispatch is that much of a killer feature. Take C# it default to static dispatch of method and you tend to get very far without explicitly making things virtual.

Event when using interfaces many times it’s seems possible to arrange things such that parametric polymorphism would be able to use a static type. There are some boiling proposals for future versions that will help with exactly this, and indeed some of the latest changes to the languages have included allowing more static dispatching in polymorphic constructs like disposal and enumeration.

I’m beginning to suspect that dynamic dispatch could be entirely dropped without losing to much expressiveness.

Have no experience with Rust but isn’t that kind of the conclusion from that community?


What's more, languages like Smalltalk which only have "virtual" dispatch achieve their performance by recognising that most dispatch is just to one type. And of the remaining cases, most of those are just to 2 types, etc. Pushing this further resulting in Strongtalk which lead to the Hotspot JVM [1].

[1] http://strongtalk.org/index.html


Not really anything of an expert with Rust, but I'd say that the language tries to dispatch function calls statically whenever possible, though it certainly has the capacity to provide polymorphism when needed, through the use of trait objects [0].

From using it personally, I've found that I can usually get by with generics and the static dispatch that they provide, but having the option to use trait bounds can be a real boon when you need the flexibility to call on shared pieces of functionality between separate complicated systems.

[0] https://doc.rust-lang.org/1.30.0/book/2018-edition/ch17-02-t...


rust has `dyn FooTrait`, which lets you do dynamic diapatch, similarly to `FooInterface x = foo();` in e.g. java. even Haskell has a way of doing this via existential types like `(forall a. FooTypeclass a => a)`.

both of these predominantly statically-dispatched langs added ways to do dynamic dispatch later on, which seems to go against "dynamic dispatch could be entirely dropped without losing to much expressiveness". i prefer static-by-default as well, but it looks like it's hard to do without sometimes.

(of course you can always roll your own vtable-ish thing, so technically you could drop DD from the language without loss of expressiveness, but then you'd likely get multiple incompatible libraries for that...)

and i'd guess that some sort of dynamic dispatch is unavoidable if you want to do some kind of plugin architecture


Are you sure that Haskell thing is dynamic? I was under the impression that the type you have there is resolved statically by finding a type class for a


existential types allow you to make it dynamic. see https://wiki.haskell.org/Existential_type, there's a section comparing it to OOP-style dynamic dispatch

simple example copied from the linked article:

  data Obj = forall a. (Show a) => Obj a

  xs :: [Obj]
  xs = [Obj 1, Obj "foo", Obj 'c']

  doShow :: [Obj] -> String
  doShow [] = ""
  doShow ((Obj x):xs) = show x ++ doShow xs
we can have a "heterogenous" list of `Obj`s, like a java `ArrayList<IPrintable>`. each `Obj` is basically a pointer to the actual value of some type T and a pointer to T's Show dictionary (vtable). Which is afaik analogous to how OOP languages do it


> I’m not sure dynamic dispatch is that much of a killer feature.

It's a feature that, when it fits your problem, can be extremely useful. I have rarely had it fit a problem. The ones it fit, though, it was nearly magic.


I think that's broadly true but it should at least be pointed out that static dispatch tends to end up inflating binary size and compilation time. Rust also supports dynamic dispatch so you can make a context dependent choice.


Static dispatch or monomorphization?


Isn’t using parametric polymorphism to get static dispatch reliant on monomorphization? That’s at least the common case in Rust.


There are languages that solve the expression problem, such as Clojure. But they aren't very popular.


In some languages runtime polymorphism is not tied to types.



OTOH, classes are just one way of doing polymorphism/dynamic dispatch. see Clojure's multimethods.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: