Angband.oook.cz
Angband.oook.cz
AboutVariantsLadderForumCompetitionComicScreenshotsFunniesLinks

Go Back   Angband Forums > The real world > Idle chatter

Reply
 
Thread Tools Display Modes
Old March 27, 2012, 18:18   #11
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,833
Derakon is on a distinguished road
Quote:
Originally Posted by AnonymousHero View Post
As far as I can tell you've just described... variables, haven't you? I mean a "Thing" is a value and a "Set" is just a collection of values (e.g. could be a list, or a bona fide set, or whatever) which is agnostic to what's actually contained in it.
Certainly what I wrote is pushing the boundaries of being too generic. And indeed the base Thing and Thing Set classes would be very bare-bones; they're only slightly smarter than the basic components that make them. They're so generic because there's relatively little that the different possible "real objects" in the game have in common -- they need to encapsulate monsters, items, traps, walls, chests, etc.

The goals are basically:

* Accomplish this encapsulation without getting in the way. You don't want a system where everything is continually climbing inheritance trees to get anything done, for example.
* Allow objects to easily find and interact with each other. This is where the registration of verbs comes in -- the game map essentially serves as a simplified database of what objects can do what with and to what other objects.
* Allow new interactions to be inserted without having to modify existing code. This is basically where object-oriented design becomes relevant. Instead of modifying the existing door code to include the possibility of trapped doors, you can subclass the Door class to make a TrappedDoor class which accepts the Disarm verb and has special logic for the Open verb if it hasn't yet been disarmed.

Quote:
To me this isn't actually the interesting bit. The interesting bit is what interactions can happen between values, i.e. "what happens when player drops an item?", "what happens when a monster shoots at the player?", etc.
I agree with you -- this is the boring stuff that underlies a good rule system. In order for you to be able to implement all of these interactions you need a good object model.

Quote:
[Wild tangent: The interactions is where currently popular OO languages go off the rails. They pretend that one of the values (Things) that takes part in an interaction is somehow "special" whereas it very seldom actually is -- compare e.g. "player.fire(projectile, monster)" versus the free function "fire(player, projectile, monster)". Multiple dispatch subsumes single dispatch and rids of this annoying and harmful asymmetry.... but I digress]
Sometimes the only difference between OO code and procedural code is one of structure, yes. There's no functional difference between a method that operates on an instance of a class, and one that accepts that instance as its first argument (indeed, in many OO languages "foo.bar()" is simply syntactic sugar for "bar(foo)").

Inheritance is useful though, and is an excellent reason for going with OO over procedural code. Players are just Creatures that listen to the keyboard and end the game when they die. All Items can be picked up, but only MeleeWeapon Items can be equipped to the weapon slot(s). And so on.
Derakon is offline   Reply With Quote
Old March 27, 2012, 18:39   #12
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,833
Derakon is on a distinguished road
Quote:
Originally Posted by AnonymousHero View Post
Actually, I think the preferred way nowadays is to use named tuples. They have the advantage of not allowing assignment to undeclared attributes:

Code:
>>> Vec = namedtuple('Vec', 'x y z')
>>> v = Vec(1, 3, 4)
>>> v.s = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Vec' object has no attribute 's'
(EDIT: ... along with actually being immutable in general )
Interesting; I didn't know about those. Thanks! Of course, immutability makes them a somewhat niche product -- excellent for things like master stats for monsters and items, not so much for particular instances of those things.

And yeah, implicit variable creation bugs me sometimes, but it's one of those "just don't do that then" types of situations. If your style guide enforces creation of all member fields in the constructor, for example, then you don't have to worry about whether or not a given instance has a specific attribute.
Derakon is offline   Reply With Quote
Old March 27, 2012, 19:59   #13
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,367
AnonymousHero is on a distinguished road
Quote:
Originally Posted by Derakon View Post
Sometimes the only difference between OO code and procedural code is one of structure, yes. There's no functional difference between a method that operates on an instance of a class, and one that accepts that instance as its first argument (indeed, in many OO languages "foo.bar()" is simply syntactic sugar for "bar(foo)").
Perhaps my example of "f(a,b,c)" didn't convey my intended meaning very well. The idea is that the selection of a concrete implementation of "f" can actually depend on the types of any, some or all of the arguments "a", "b", and "c"... whereas in the currently popular OO models the selected implementation of "f" can only depend on the type of "a" (the first/privileged argument).

(This is also called "multimethods".)

Quote:
Originally Posted by Derakon View Post
Inheritance is useful though, and is an excellent reason for going with OO over procedural code. Players are just Creatures that listen to the keyboard and end the game when they die. All Items can be picked up, but only MeleeWeapon Items can be equipped to the weapon slot(s). And so on.
I find that people very often conflate two conceptually distinct ideas: Subtyping and implementation inheritance. (It's not really their fault -- mainstream languages practically force you into this mode of thinking.) Subtyping is an extremely stringent requirement which models an "is-a" relationship (should basically obey the Liskov substitution principle, that is you should always be able to use one in place of the other) -- very few things actually have such a relationship. Implementation inheritance is a simple matter of convenience and DRY (which is good), but in mainstream languages this automatically implies some sort of subtyping relationship (which is bad, very bad).

I'm not arguing against (implementation) "inheritance" as such, I'm arguing against the conflation of subtyping and impl. inheritance. I find that using free functions or multi-methods helps ward off this conflation.
AnonymousHero is offline   Reply With Quote
Old March 27, 2012, 20:19   #14
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,367
AnonymousHero is on a distinguished road
Quote:
Originally Posted by Derakon View Post
(Immutability vs. mutability)
IME choosing immutability leads to more obvious code, and far fewer bugs. As a bonus it often also makes testing a lot easier since f(a) will always return the same value given the same value of "a". If you allow mutability this guarantee breaks very easily since "f" could access all kinds of more-or-less global (mutable) state. Obviously you're going to have to mutate some state at some point, but my philosophy is just that all state mutation should be pushed towards the "edges" of the program.

Perhaps Python is too much in the "mutable" camp at this point to make large-scale "immutable programs" impracticable. However, I don't see any particular reason that the monster instances, object instances, etc. shouldn't be immutable.

Quote:
Originally Posted by Derakon View Post
And yeah, implicit variable creation bugs me sometimes, but it's one of those "just don't do that then" types of situations. If your style guide enforces creation of all member fields in the constructor, for example, then you don't have to worry about whether or not a given instance has a specific attribute.
I don't see how a style guide can protect you unless your style guide specifically tells you must override __setattr__ (or whatever it's called) to forbid assignment outside of the constructor.

(Of course you could perhaps implement a @decorator which does this. I think this might already exist, actually...)

Anyway, this is all pretty vague -- I just want to caution against perceiving OO as a panacea for fixing Angband. I think most of us agree that it isn't -- I just wanted to call attention to it because people with less experience may perceive it as such... and that would be a mistake.

Over and (probably) out.
AnonymousHero is offline   Reply With Quote
Old March 27, 2012, 20:43   #15
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,833
Derakon is on a distinguished road
Quote:
Originally Posted by AnonymousHero View Post
IME choosing immutability leads to more obvious code, and far fewer bugs. As a bonus it often also makes testing a lot easier since f(a) will always return the same value given the same value of "a". If you allow mutability this guarantee breaks very easily since "f" could access all kinds of more-or-less global (mutable) state. Obviously you're going to have to mutate some state at some point, but my philosophy is just that all state mutation should be pushed towards the "edges" of the program.

Perhaps Python is too much in the "mutable" camp at this point to make large-scale "immutable programs" impracticable. However, I don't see any particular reason that the monster instances, object instances, etc. shouldn't be immutable.
I have to admit I haven't done much work with immutable objects being central to a large-scale project. I'm not certain what you mean by "pushing state mutation to the edge of the program"; care to explicate? I do recognize the value in being able to call a function with a specific argument and knowing that that function won't change that argument though.

Quote:
I don't see how a style guide can protect you unless your style guide specifically tells you must override __setattr__ (or whatever it's called) to forbid assignment outside of the constructor.

(Of course you could perhaps implement a @decorator which does this. I think this might already exist, actually...)
A style guide protects you from people screwing up if you forbid checkins that don't follow the style guide. Of course you could still theoretically write and check in code that breaks the guide. It's like how most door locks are just there to keep honest people from being tempted, not as any significant form of security. Just because they can be relatively easily bypassed doesn't mean they will be.

Quote:
Anyway, this is all pretty vague -- I just want to caution against perceiving OO as a panacea for fixing Angband. I think most of us agree that it isn't -- I just wanted to call attention to it because people with less experience may perceive it as such... and that would be a mistake.

Over and (probably) out.
Oh absolutely. You can write bad code in any language and with any particular design style you care to use. I have seen some absolutely wretchedly bad OO Python code in my time, to the extent that I'd say it was intentionally obfuscated if I didn't know better.

I've enjoyed our discussion, anyway. Being a programmer requires this weird mix of hubris and humility, as you defend your chosen design while being open to the possibility that you might be wrong.
Derakon is offline   Reply With Quote
Old March 27, 2012, 21:08   #16
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,367
AnonymousHero is on a distinguished road
Quote:
Originally Posted by Derakon View Post
I have to admit I haven't done much work with immutable objects being central to a large-scale project. I'm not certain what you mean by "pushing state mutation to the edge of the program"; care to explicate? I do recognize the value in being able to call a function with a specific argument and knowing that that function won't change that argument though.
Sure -- I'll try to respond in-depth on this tomorrow.

Quote:
Originally Posted by Derakon View Post
A style guide protects you from people screwing up if you forbid checkins that don't follow the style guide. Of course you could still theoretically write and check in code that breaks the guide. It's like how most door locks are just there to keep honest people from being tempted, not as any significant form of security. Just because they can be relatively easily bypassed doesn't mean they will be.
I was thinking of the specific situation of code like
Code:
x.aFieldName = 123
where you really meant
Code:
x.aFieldname = 123
Reviews may help in detecting such cases, but this kind of mistake is a) difficult for humans to detect (we're annoyingly good at "overlooking" such mistakes because of our overactive pattern matching esp. when reading) and b) hard to detect by testing.

I'd much rather have
Code:
x.aFieldName = 123
throw an exception immediately rather than simply creating a new attribute on "x" named "aFieldName" (and leading to subtle bugs later). This would make such typos much easier to detect in testing since the error would trigger as soon as you hit that line.

You might say I'm a sucker for the mantra of "detect errors/failure as early as possible"... which is not easy with dynamically type checked languages like Python, but every little helps.
AnonymousHero is offline   Reply With Quote
Old March 27, 2012, 21:26   #17
ekolis
Knight
 
ekolis's Avatar
 
Join Date: Apr 2007
Location: Cincinnati, OH, USA
Age: 35
Posts: 911
ekolis is on a distinguished road
Send a message via AIM to ekolis Send a message via MSN to ekolis Send a message via Yahoo to ekolis
Quote:
Originally Posted by AnonymousHero View Post
However, I don't see any particular reason that the monster instances, object instances, etc. shouldn't be immutable.
Wouldn't that require instantiating a new monster instance whenever an existing monster instance takes damage? Or are you saying that only the references themselves should be immutable, but their attributes can be mutable?

e.g.

Monster m = SomeFunctionWhichGetsAMonster();
m.Hitpoints -= 5; // ok
m = SomeOtherFunctionWhichGetsAMonster(); // not ok
__________________
You read the scroll labeled NOBIMUS UPSCOTI...
You are surrounded by a stasis field!
The tengu tries to teleport, but fails!
ekolis is offline   Reply With Quote
Old March 27, 2012, 21:30   #18
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,833
Derakon is on a distinguished road
Yes, I agree that it'd be preferable to error instead of creating a new field. And you're right that this isn't the kind of thing that code reviews will necessarily catch. Unfortunately, while it is apparently possible to enforce no new field creation post-constructor, it requires some rather unpleasant hackery since there appears to be no way to swap out the __setattr__ function at runtime.
Derakon is offline   Reply With Quote
Old March 28, 2012, 06:13   #19
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,367
AnonymousHero is on a distinguished road
Quote:
Originally Posted by ekolis View Post
Wouldn't that require instantiating a new monster instance whenever an existing monster instance takes damage? Or are you saying that only the references themselves should be immutable, but their attributes can be mutable?
Yes, a new instance. In the case of Python's named tuples, what you'd do is
Code:
>>> p = Point(x=11, y=22,z=44)
>>> p2 = p._replace(x=33, z=55)
Point(x=33, y=22,z=55)
The idea is that: 1) you just copy all the fields except the ones you override, and 2) if you make sure that your named tuples only refer to other immutable objects, then you can use shallow copies rather than deep copies of the fields. The latter makes the "update" fast.
AnonymousHero is offline   Reply With Quote
Old March 28, 2012, 06:16   #20
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,367
AnonymousHero is on a distinguished road
Quote:
Originally Posted by Derakon View Post
Yes, I agree that it'd be preferable to error instead of creating a new field. And you're right that this isn't the kind of thing that code reviews will necessarily catch. Unfortunately, while it is apparently possible to enforce no new field creation post-constructor, it requires some rather unpleasant hackery since there appears to be no way to swap out the __setattr__ function at runtime.
That doesn't look too bad to me. Obviously you'd want to hide this away in a decorator or something for easy use, so you'd be able to write something like:

Code:
@struct
class X:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
and have the decorator take care of the rest.
AnonymousHero is offline   Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Roguelike Radio andrewdoull Idle chatter 47 November 8, 2014 21:57
Random theorycrafting: "Pyrl" Derakon Variants 5 December 29, 2011 15:59
Roguelike Phylogeny Zappa Vanilla 5 August 14, 2009 22:32
Rationale for new stat gain model? Pete Mack Vanilla 8 February 10, 2009 22:01
Roguelike development diary andrewdoull Variants 0 May 14, 2007 11:35


All times are GMT +1. The time now is 05:15.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2019, vBulletin Solutions Inc.