FYI, I recently found out that C# can actually do multiple dispatch
https://blogs.msdn.microsoft.com/shawnhar/2011/04/05/visitor...
FYI, I recently found out that C# can actually do multiple dispatch
https://blogs.msdn.microsoft.com/shawnhar/2011/04/05/visitor...
As you've found, overloading is statically typed multiple dispatch; discovering the right overloaded method at runtime is functionally equivalent to multiple dispatch if your overload resolution rules prefer more specific derived types (rather than simply giving up with ambiguity).
http://pzol.github.io/getting_rusty/posts/20140417_destructu...
It's not just about finding the right bit of code to run; it's also about pulling some fields out of the polymorphic value so they're available as nice short names, without qualification.
C# is a weird beast though - it's pure OO on some level, but then keeps making getters and setters even easier to write. It's easier to write an "object" where every field has a getter and setter, than it is to write an actual constructor for private variables, so that's what people do.
What I want to do is create proper sum types. For some reason most OO languages were designed around the idea that being "open for extension" was a good thing. Normally if I make a base class, I want a closed and known set of sub classes. E.g. if I make a payment method then I want to make a Credit or Invoice kind where one requires Credit Card details and the other requires an address. In F# that's a simple sum type. In C# making a sum type is a convoluted mess of making an outer base class and private nested sub classes.
Here a sum type with the two cases plus an exhaustive pattern matching switch would have solved in 10 lines what takes me 300 lines to do in C#. I think the concept of "fixed" or "closed" hierarchies is foreign enough to OO that it probably needs to be a completely separate concept from the normal types. F# shows that it can be implemented as sugar on top of the regular CLR type system. All that's needed is that some keyword such as "enum class" is converted into a sealed hierarchy of plain record classes with no methods on them. The switch statement we already have can then treat these "enum classes" specially by checking that all cases are matched.
A sketch solution for C# could look like
enum class PaymentMethod
{
CreditCard(CrediCardDetails cc),
Invoice(Address billingAddress),
}
// later
switch (paymentMethod)
{
case CreditCard(ccdetails):
Console.WriteLine($"Creditcard expiring {ccdetails.ExpiryDate}");
case Invoice(addr):
Console.WriteLine($"Bill to {addr.Name}");
}
Writing the above in current C# would require significantly more boilerplate.
This isn't the best pattern to use in all situations: but in many cases when the types are merely data carriers it doesn't make sense to put the logic in the class as OO prescribes, so the FP recipe works much better.The example they give for notification = email | sms | voice looks a lot like what I was trying to sketch with the paymentMethod example.
http://docs.scala-lang.org/tutorials/tour/case-classes.html
I didn't intend for my proposal to be more limited than than the Scala case classes at least :). I want exactly that. An FP closed type hierarchy with enforced pattern matching.
They also have more configurability - while a case class provides default implementations for things like equality, you can override those (this could be a pro or a con depending on whether you prefer flexibiltiy or performance, I guess - although arguably the flexibility should only have cost if you use it, assuming it's implemented well).
It's definitely not a huge difference, and arguably Scala can suffer from it's philosophy of providing tools that can do something rather than tools for something that means the language often comes across as huge and as having too much stuff in it. I like it, but it's not the C# way right now for sure.
It's equivalent of
_ -> default_function
At the end of a pattern matching statement.Essentially it's impossible for not all the cases to be matched, as far as I can see.
What I want is to make a new type added mean the code won't compile until it is explicitly handled in all pattern matches that do not have a default clause. Basically what I'm saying is that this:
bool HandlePayment(PaymentMethod pm) {
switch(pm) {
case Invoice(addr):
SendInvoice(addr);
return true;
case CreditCard(ccdetails)
return PrcessCreditcard(ccdetails);
default:
// What?
}
}
Would be a lot better if it didn't require the default, and the compiler could detect the error that occurs when someone adds a new case (BitCoin) to the types of payment method. Inheritance and abstract baseclass for the processing means that you'd do this bool HandlePayment(PaymentMethod pm)
{
return pm.Process(order); // sends an invoice, charges credit card etc
}
and you simply have
abstract class PaymentMethod { abstract bool Process(order); }
class CreditCard : PaymentMethod { ... }
class Invoice : PaymentMethod { ... }
And this is of course the regular way of writing OO, but I find it unnatural and cumbersome in many cases. It's hard to define concise descriptions of what types are actually available, and you have to write a ton of boilerplate to ensure a closed hierarchy and complete matching in all scenarios where you can't enclose the logic inside each type.For example, if I am writing a logic system in rust I can either match with one single arm or a nested match implement simple rules like double negation. I don't see how you could easily do this with multiple dispatch like the one you link.
I find myself really wish more languages would implement both the system you link and something like rust's/ML's. Usually it's easier to build multi-dispatch than match syntax though. In rust you can use HashMaps of TypeId -> Box<Any>. I would do a similar thing in C# (though now I am going to use this shiny new feature).
> "I don't want to implement the visitor pattern"
Both multiple dispatch and visitor bring type unsafe dynamism and possible runtime errors. Pattern matching and algebraic types fit well into static type system and are much more safe.
I think that Alan Key spoke about dynamic nature of OOP, that's one of the examples. Even in the statically typed environment OOP demonstrates its dynamic nature.
class Expression {}
class Add : Expression {}
class Const : Expression {}
class Var : Expression {}
void FSpecialization(Expression e1, Expression e2) { ... }
void FSpecialization(Const c, Var v) { ... }
...
void F(Expression e1, Expression e2)
{
FSpecialization(e1 as dynamic, e2 as dynamic);
}
How is this not exhausted? Any new sub class of expression will be routed through to the first FSpecialization. Same as it would with a _ -> default_f
in algebraic pattern matching.If you would remove FSpecialization(Expression e1, Expression e2) a.k.a wildcard pattern, compiler would not warn you about your dispatching is not exhaustive. And if you'd add another type of expression, all your dispatchers and visitors without wildcard would become non exhaustive, staying valid code from a compiler perspective at the same time.
Because what happens if you introduce a new expression, but not realize this means the implementation of F should be updated?
Regarding algebraic pattern matching, you can let Haskell check exhaustiveness and then drop the default case. The compiler will then warn you if you forgot one.
To give you an example, in our codebase we have an enum that is used 3068 times in one solution. A very similar one (you would need domain knowledge to understand the difference) is used 2985 times. Both are not defined in that solution by the way.
It's not out of the question they will receive a new enum member down the line. It would be very useful if the compiler was able to tell you a switch that uses either enum is no longer exhaustive.
If you could exhaustively switch on an enum, you could skip the default: case and have the compiler enforce you covered all cases. (I know, the C# enum==int would make that impossible.)
But also good to know, that it can be costly and unsafe. `dynamic` is basically using reflection at run-time, which for most cases is fine, but it's something to keep in mind. It also means that type checking is delayed until run-time instead of compile-time.
I think the safety thing is a non issue as well - what arguments could you pass in to the statically typed function that would cause the use of dynamic to bite you in the ass?
A dynamic "type" is really just `object` whose methods are looked up at run time. It's not tagged data at all. See [1] for more info, specifically the example(s) at the bottom, as the reflection code is what gets executed at run (albeit with caching so that methods aren't looked up all the time).
`dynamic` is a complex feature that enables multiple dispatch as a side-effect, but only because it allows a whole lot more.
[1]: https://visualstudiomagazine.com/Articles/2011/02/01/Underst...
> what arguments could you pass in to the statically typed function that would cause the use of dynamic to bite you in the ass
public void Output(int value);
public void Output(Person person);
...
dynamic aValue = "Some String";
Output(aValue);
Compiles, because the actual resolving of which overload to call is done at runtime, not at compile time. And at runtime, there is no overload that accepts a string.Dynamically typed languages usually represent objects as something like a struct with an int tag to represent the type, and a void pointer for its value. The actual reflection going on in the code I posted for multiple dispatch would surely be nothing more than an int comparison - same as what Ocaml probably does.
Ah, I misunderstood the question. Yes, in that case it's fairly type-safe.
> The actual reflection going on in the code I posted for multiple dispatch would surely be nothing more than an int comparison
Is this based on gut reaction or are you talking about optimizations that `dynamic` performs? I ask because my understanding is that `dynamic` is like a really efficient reflection-emitter, that attempts to do at runtime the same thing that the compiler would do at compile time, but slower because it has to look stuff up via reflection.
From Eric Lippert [2]:
> The magic is: the compiler emits code that starts the C# compiler again at runtime. The runtime version of the compiler analyzes the call as though the compile-time types of all the objects had been their actual runtime types, generates an expression tree representing that call, compiles the expression tree, caches the delegate for next time, and runs the delegate.
[2]: http://stackoverflow.com/questions/10330805/c-sharp-multiple...
So maybe `dynamic` at runtime figures out that it can do a "a struct with an int tag to represent the type, and a void pointer for its value", but I wouldn't assume it does and from what I've read on it, I wouldn't think that it does.
But to answer your question - yes, entirely based on gut reaction and what a compiler would ideally be able to do given the code posted. So very probably wrong.
None if the function is written correctly, but by that logic you don't need type safety at all. The point is that the dynamic technique gives you no warning if you forget to implement one of the cases.
void React(Animal me, Animal other)
{
ReactSpecialization(me as dynamic, other as dynamic);
}
Unless you pass in a casted value here, it's fool proof.As an OO apologist I do feel compelled to point out that
type Animal = Cat | Dog | Mouse
let react = function (a, b)
| Cat, Dog -> ...
...
| x, y -> $"{x} is not interested in {y}."
Would suffer from the exact same issue though.Basically, this is going to be about as fast as you can get with the statically-typed underlying runtime.
Can you expand on this? I would not have expected that it would be faster than compiled expressions.
[1]: http://geekswithblogs.net/simonc/archive/2012/07/20/inside-t...