Can people list some times when they actually used multimethods to solve a real problem? How did it work out? Would you do it again?
Can people list some times when they actually used multimethods to solve a real problem? How did it work out? Would you do it again?
One case where I actually rely on multiple dispatch is conversion to more or less structured data. Inspired by Clojure, I have a function `conform(OutputDataType, input_data::SomeType)` and specific implementations depend on both types involved.
Multiple dispatch is also really helpful when two libraries don't quite do the same thing. In python pandas, numpy, and pytorch all have slightly different implementations of x.std() (standard deviation) with slightly different APIs. This means you can't write code that's generic across a numpy array or a pytorch tensor. In Python this could only easily be fixed if library authors coordinate, but with multiple dispatch you can just fix this in your own code.
In particular, the fact that these types would silently give you different answers if you called `.std()` was a big headache
It was very common for us to want to be generic over pandas series and numpy arrays. A bit less so with pytorch tensors, but that was because we just aggressively converted them to numpy arrays. Fundamentally these three are all very similar types so it's frustrating to have to treat them differently.
I was writing unit tests once against histograms. That code is super finnicky, and I couldn't get pandas and polars numbers to tie out. I wasn't super concerned with the exact output for my application, just that number of buckets was the same and they were roughly the same size. Just bumping to a new version of numpy would result in test breakages because of floating point rounding errors. I'm sure there are much more robust numerical testing things I could have done
Before switching to Julia we eventually standardized on numpy.std with ddof=1, and with some error tolerances in our tests.
One case where I'm finding it extremely useful is that I'm currently using ovld to implement a serialization library (https://github.com/breuleux/serieux, but I haven't documented anything yet). The arguments for deserialization are (declared_type, value, context). With multimethods, I can define
* deserialize(type[int], str, Any) to deserialize a string into an int
* deserialize(type[str], Regexp[r"^\$"], Environment) to deserialize $XYZ as an environment variable lookup but only if the context has the Environment type
* deserialize(type[Any], Path, WorkingDirectory) to deserialize from a file
That's one situation where ovld's performance and codegen capabilities come in handy: the overhead is low enough to remain in the same performance ballpark as Pydantic v2.
I know that dependent type is the term of art, and you should probably keep it. You could say something along the lines of
"ovld supports dependent types (an additionally specific name for a type that is based on its value, ie > 0)" the first time you use the term dependent types."
Example: turning a data structure into a JSON string, solved with multi dispatch: https://github.com/moritz/json/blob/master/lib/JSON/Tiny.pm#...
Another common useful example is constructors. A Date class might have constructors that accept
Date(Int, Int where 1..12, Int where 1..31) # Y, M, D
Date(Str where /^\d4-\d2-\d2$/) # YYYY-MM-DD
Date(DateTime) # extract the date of a DateTime object
etc. to make it very intuitive to use.If the language is dynamically typed, you can use it for polymorphism.