proj-oot-old-ootTypeNotes7Old

---

my notes on the following series of blog posts by Eric Lippert, C# guy:

[1] [2] [3] [4] [5]

Imagine you had this sort of situation:

    A wizard is a kind of player.
    A warrior is a kind of player.
    A staff is a kind of weapon.
    A sword is a kind of weapon.
    A player has a weapon.

In OOP, say in C#, you may be tempted to make the following classes: Player, Weapon, make the obvious subclasses, and make a member variable in Player of type Weapon. But then what if we add the following:

    A warrior can only use a sword.
    A wizard can only use a staff.

and

    wizards and warriors can both use daggers (pretend the user only thinks up this constraint later, after the rest of the program is already written)

"Readers familiar with type theory will know that the highfalutin name for the problem is that we’re in violation of the Liskov Substitution Principle."

Lippert goes thru a bunch of proposals and finds no good way to represent this in C#. You can do it, but only at runtime; almost every solution might throw an exception at runtime if you try to violate the constraints.

The best way he finds (imo) is to use generics:

" abstract class Player<TWeapon> where TWeapon : Weapon { TWeapon Weapon { get; set; } } sealed class Wizard : Player<Staff> { } sealed class Warrior : Player<Sword> { } " (and also make Player<TWeapon> inherit from some BasePlayer?, so that there's a way for non-generic methods to accept Players)

but e finds this ugly because:

then he also discusses a totally different problem:

"Werewolf and Vampire classes... are a kind of Monster ... if a Warrior tries to hit a Werewolf after midnight then the probability of success is lowered. (Wizards have no such penalty because… magic?"

again, he tries a bunch of things and finds no good way to handle this at compile time. The problem is that you need multiple-dispatch and C# only supports single-dispatch. He finds two sorta-good ways to do it:

1) have a bunch of boilerplate (called the Visitor Pattern) where the Player classes delegate the task of dealing with Attack to the monster classes:

sealed class Warrior : Player { public override void Attack(Monster monster) { monster.ResolveAttack?(this); } }

now you can have stuff like:

sealed class Werewolf : Monster { public override void ResolveAttack?(Warrior player) { special logic for Warrior vs Werewolf goes here } }

he doesn't like this because:

2) use C#'s "dynamic" to achieve multiple dispatch directly, but at runtime instead of compile-time:

" abstract class Player { not virtual! public void Attack(Monster monster) { dynamic p = this; dynamic m = monster; p.ResolveAttack?(m); }

  public void ResolveAttack(Monster monster)
  {
    // basic case code goes here
  }} sealed class Warrior : Player {  not virtual! public void ResolveAttack?(Werewolf monster) {  Warrior vs Werewolf code goes here } } "

"How does this work? Remember, the fundamental rule of dynamic is do what the compiler would have done had the compiler known the runtime type of every dynamic expression."

Why do you need 'dynamic'?

" If we had written

  public void Attack(Monster monster)
  {
    dynamic p = this;
    p.ResolveAttack(monster); // argument is not dynamic
  }

then this is treated as

((Warrior)p).ResolveAttack?(monster);

and clearly overload resolution would not choose Warrior.ResolveAttack?(Werewolf). "

He doesn't like this because it's still doing everything at runtime; (a) the compiler is being invoked at runtime to do type stuff, and (b) we won't get compile-time errors if we screw up.

He also notes that "why is resolving “a Paladin in the Church attacks a Werewolf with a Sword” a concern of any one of those types, over any other? Why should that code go in the Paladin class as opposed to, say, the Sword class?"

as an aside, he notes two potential difficulties with this approach;

" class Warrior : Player { public void ResolveAttack?(Werewolf monster, HolyGround? location) { Warrior vs Werewolf on Holy Ground code goes here } }

sealed class Paladin : Warrior { public void ResolveAttack?(Monster monster, HolyGround? location) { Paladin vs any Monster on Holy Ground code goes here } }

If a Paladin attacks a Werewolf on Holy Ground, what happens? On the one hand we have the rule that the more specific argument type is better. Werewolf is more specific than Monster, but Paladin is more specific than Warrior, so which wins? C# says that the receiver is special; a method in a more derived class always wins. That might be unexpected. "

and

" Worse, you could easily end up in situations that would produce an error at compile time, and therefore in the dynamic invocation, produce an exception at runtime:

sealed class Paladin : Warrior { public void ResolveAttack?(Monster monster, HolyGround? location) { Paladin vs any Monster on Holy Ground code goes here } public void ResolveAttack?(Werewolf monster, Location location) { Paladin vs Werewolf in any location code goes here } }

Now if a Paladin attacks a Werewolf on Holy Ground, what happens? Neither method is clearly better than the other, and so the dynamic dispatch will produce an error at runtime. "

Finally, he concludes that both problems ((1) the problem of how to encode the constraint that Warrior <: Player and Wizard <: Player, and Player has a Weapon, but there are certain Weapons that Wizards can't use, etc; and (2) the problem of how to encode the constraint that "if a Warrior tries to hit a Werewolf after midnight then the probability of success is low") are trying to use the C# type system for stuff that it can't handle:

"detect and prevent violations of the rules of the business domain at compile time. That effort has largely failed, due to the difficulty of representing a subtype with a restriction"

"The fundamental problem is my initial assumption that the business rules of the system are to be expressed by writing code inside methods associated with classes in the business domain — the wizards and daggers and vampires. We keep on talking about “rules”, and so apparently the business domain of this program includes something called a “rule”, and those rules interact with all the other objects in the business domain. So then should “rule” be a class? I don’t see why not! It is what the program is fundamentally about. It seems likely that there could be hundreds or thousands of these rules, and that they could change over time, so it seems reasonable to encode them as classes."

"...we have no reason to suppose that the rules of overload resolution and the rules of Dungeons & Dragons attack resolution have anything in common"

a comment states:

" gregsdennis on May 11, 2015 at 11:04 am said:

An interesting point that I’m surprised was left out of this article is that you have perfectly illustrated why we need to follow the Single Responsibility Princple. Players and weapons are objects; they track what they are and their condition/state. Their interactions with other objects is not their concern. That should be left to some object which only concerns itself with those interactions. Reply ↓

    ericlippert on May 11, 2015 at 11:15 am said:
    Indeed — I have not mentioned the SRP by name, but the idea of “what is the concern of this class?” has cropped up many times in this series."

as an aside, another comment notes:

" supercat on May 11, 2015 at 8:47 pm said:

One of my complaints about object-oriented languages is that they impose too strong a coupling between the combinations of features seen by clients and the code structure of the underlying implementations. If client code has references to two player objects and a weapon object, it should be able to ask the first player object to attack the second player object with the weapon without having to worry about the existence of a rulebook object.

This sort of issue comes up in a lot of real-world situations involving graphics. If I have an object whose `Paint` method needs to draw some shapes on a `Graphics` object, and most of those shapes represent operations built into `Graphics` but a few don’t, it can be awkward to have a paint method which accepts `Graphics gr` as a parameter say

  gr.DrawLine(...);
  gr.DrawLine(...);
  GraphHelpers.DrawRoundRect(gr, ...);
  gr.drawLine(...);"

Instead, he recommends reifying the rules and doing this:

" The fundamental concerns of the program are users, commands, game state, and rules. A user provides a sequence of commands. A command is evaluated in the context of the rules and current game state, and produces an effect. ... A player has a weapon, great, that’s fine, we’ll make a Player class with a property of type Weapon. That code makes no attempt to try to represent that a wizard can only wield a staff or a dagger; all that code does is keep track of game state, because state is its concern.

Then we make a Command object called Wield that takes two game state objects, a Player and a Weapon. When the user issues a command to the system “this wizard should wield that sword”, then that command is evaluated in the context of a set of Rules, which produces a sequence of Effects. We have one Rule that says that when a player attempts to wield a weapon, the effect is that the existing weapon, if there is one, is dropped and the new weapon becomes the player’s weapon. We have another rule that strengthens the first rule, that says that the first rule’s effects do not apply when a wizard tries to wield a sword. The effects for that situation are “make a sad trombone sound, the user loses their action for this turn, no game state is mutated”. "

He recognizes that the infrastructure for dealing with rules is heavyweight, but thinks it is necessary, esp. because the rules will probably change more rapidly than other parts of the program. Presumably to soften the blow, he also points out some ancillary benefits for them being first-class.

" ...we need to design a system that correctly chooses the valid rules out of a database of rules, and composes the effects of those rules sensibly. Yes, you need to build your own resolution logic, but resolving these rules is the concern of the program, so of course you’re going to have to write code to do it.

And what new scenarios have we enabled? Rules are now more like data than like code, and that is powerful!

    We can persist rules into a database, so that rules can be changed over time without writing new code. And we get all the nice benefits of a database, like being able to roll back to previous verions if something goes wrong.
    We can write a little Domain Specific Language that encodes rules as human-readable text.
    We can do experiments, trying out tweaks to the rules without recompiling the program.
    We saw in a previous episode that it might be hard to know which of several rules to choose from, or how to combine the effects when multiple rules apply. We can write test engines that try billions of possible scenarios and see if we ever run into a situation where the choice of applicable rules becomes ambiguous or violates a game invariant, and so on.

This kind of system, where rules are data, not code, seems like it would be quite a bit more heavyweight than just encoding the rules in C# and its type system, but it is also more flexible. I say that when the business domain of the program actually is evaluating complex rules and determining their actions, and particularly when those rules are likely to change over time more rapidly than the program itself changes, then it makes a lot of sense to make rules first-class objects in the program itself. "

my question is, is this the sort of thing that more powerful type systems can handle? It seems to me that he finds some dynamic (by which i mean, at runtime) solutions, and some boilerplate solutions, and rejects them, saying that what e really wants is compile-time checking (without killing readability). But then at the end he says, C# just can't handle this. It seems to me that it would be good for the type system to handle this, because these are certainly situations involving compile-time proofs that variables at certain positions in the code are of certain types.

For the second problem, a language with multiple dispatch could probably handle it; he mentions two potentially confusing situations as an aside, but i don't think they're fatal. For the first problem, i'm not immediately sure what to do, because he's right, Liskov requires that any subtype of Weapon must be able to replace any occurrence of Weapon, full stop, and a Player has a Weapon, so a Staff must be able to be that Weapon (for any Player). I'm guessing that there's some way to handle this in some type system, though; perhaps his idea of using generics works, and we just need better syntax for that.

Or perhaps we leave Staff and Weapon alone, and we need to have some new construct that goes at the overrides of the 'weapon' member field definition in Player in its subclasses Warrior and Wizard, which says 'Yes, this thing must still be a Weapon, but here are some other constraints'.

---