
Hi, I'm writing a game in Haskell. The game state includes a lot of closures. For example, if a game object wants to trigger an event at a particular time, it adds a function (WorldState -> WorldState) to a queue. Similarly there are queues which contain lists of functions which respond to events. (CreatureAttackEvent -> WorldState -> WorldState) I'd like to be able to save the game state to disk so that it can be reloaded. Obviously, these closures are now a problem, as they can't be stored. I could obviously, replace all the functional values with non-functional ones, and have a big case statement specifying the behaviour associated with each constant. However, I don't really like this solution. It means that for each type of function which was in the game state (there are lots) I will need a type which lists all the possible behaviours (again, there are lots) that could occur. First of all, these types are likely to be very large. Secondly, I'd rather the behaviours were defined in different modules. For example, I want to have an "Orc" module which defines functions such as "Response to orc being hit", "Response to orc picking something up", etc. This implementation would force me to have a "Monster reponse to being hit" MODULE, containing an orcs response, a goblins response etc. I.e., the modules would be grouped by type of behaviour, rather than type of monster, meaning that adding a new monster type would require modifications accross many modules. (This example is obviously something of a simplification, but hopefully it shows the point.) Obviously, none of this is fatal. I could make it work. But going through my program and replacing all the functions with values and then doing big pattern matches against the values to find the functions seems like a very non-functional way of doing things. Can anyone suggest a neater design?

On Fri, Jan 30, 2004 at 09:21:58AM -0700, nickgrey@softhome.net wrote:
Hi,
I'm writing a game in Haskell. The game state includes a lot of closures. For example, if a game object wants to trigger an event at a particular time, it adds a function (WorldState -> WorldState) to a queue. Similarly there are queues which contain lists of functions which respond to events. (CreatureAttackEvent -> WorldState -> WorldState)
I'd like to be able to save the game state to disk so that it can be reloaded. Obviously, these closures are now a problem, as they can't be stored.
I could obviously, replace all the functional values with non-functional ones, and have a big case statement specifying the behaviour associated with each constant. However, I don't really like this solution.
Just a vague idea: I think it would be possible to apply some kind of automatic whole-program transformation that would do most (if not all) the work for you. The relevant keywords may be "defunctionalisation", "closure conversion". Do you want to be able to save game in one version of the program and restore it in a newer version? This can be difficult. Best regards, Tom -- .signature: Too many levels of symbolic links

I'm rather "shooting from the hip" here (that is, I haven't really thought this through).. I wonder if the "Scrap your boilerplate" [1] approach might apply here. I'm thinking that it might allow you to separate the behaviour ("Response to being hit") from the thing that responds ("orc"), so that you can, for example, collect the various behaviours in a module that defines "orc", and different behavours for "troll", "wizard", etc. Then I would imagine the action "hit X" would be applied as a generic function traversing the game state, or part of the game state. Components of the state that don't understand the concept would not respond. Adding a new behaviour or stimulus should mean no change to the components that don't need to recognize that behaviour. Just a thought. #g -- [1] http://www.cs.vu.nl/Strafunski/gmap/ At 09:21 30/01/04 -0700, nickgrey@softhome.net wrote:
Hi, I'm writing a game in Haskell. The game state includes a lot of closures. For example, if a game object wants to trigger an event at a particular time, it adds a function (WorldState -> WorldState) to a queue. Similarly there are queues which contain lists of functions which respond to events. (CreatureAttackEvent -> WorldState -> WorldState) I'd like to be able to save the game state to disk so that it can be reloaded. Obviously, these closures are now a problem, as they can't be stored. I could obviously, replace all the functional values with non-functional ones, and have a big case statement specifying the behaviour associated with each constant. However, I don't really like this solution. It means that for each type of function which was in the game state (there are lots) I will need a type which lists all the possible behaviours (again, there are lots) that could occur. First of all, these types are likely to be very large. Secondly, I'd rather the behaviours were defined in different modules. For example, I want to have an "Orc" module which defines functions such as "Response to orc being hit", "Response to orc picking something up", etc. This implementation would force me to have a "Monster reponse to being hit" MODULE, containing an orcs response, a goblins response etc. I.e., the modules would be grouped by type of behaviour, rather than type of monster, meaning that adding a new monster type would require modifications accross many modules. (This example is obviously something of a simplification, but hopefully it shows the point.) Obviously, none of this is fatal. I could make it work. But going through my program and replacing all the functions with values and then doing big pattern matches against the values to find the functions seems like a very non-functional way of doing things. Can anyone suggest a neater design? _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
------------ Graham Klyne For email: http://www.ninebynine.org/#Contact

On Fri, Jan 30, 2004 at 09:21:58AM -0700, nickgrey@softhome.net wrote:
Hi,
I'm writing a game in Haskell. The game state includes a lot of closures. For example, if a game object wants to trigger an event at a particular time, it adds a function (WorldState -> WorldState) to a queue. Similarly there are queues which contain lists of functions which respond to events. (CreatureAttackEvent -> WorldState -> WorldState)
I'd like to be able to save the game state to disk so that it can be reloaded. Obviously, these closures are now a problem, as they can't be stored.
It seems like there are two things you want to do with these functional closures: save them to disk, and run them as functions. Why not combine these two into a type class? Peace, Dylan

Dylan Thurston writes:
It seems like there are two things you want to do with these functional closures: save them to disk, and run them as functions. Why not combine these two into a type class?
Dylan, This is the kind of thing I'd like to do, but I can't see how to do it. (Not that I'm claiming it's impossible, I'd played with Haskell on and off for a couple of years now, but I'm a long way from being an expert.) class StoreableFunc1 a where store :: a -> String unstore :: String -> a apply :: ?? -- What type here?? Probably a multi-parameter class would be appropriate, but again I can't really see how: class StorableFunc2 (f a) where store :: f a -> String unstore :: String -> f a apply :: f (b -> c) -> b -> f c -- Would need a type roughly like this -- But this is obviously wrong Of course, I could do: class StorableFunc3 (f a) where store :: f a -> String unstore :: String -> f a unwrap :: f a -> a But then, once you chose to apply one value a function, it is no longer storable. I have actually managed to hack something together which satisfies most of my requirements. I have a type (ExtFunc a) which is an instance of Read and Show. There are the following functions: apply :: forall b a. (Show a) => ExtFunc (a -> b) -> a -> ExtFunc b unwrap :: forall a. ExtFunc a -> a So, ExtFuncs can be used just like functions, and can be shown and read, even when partially applied, which I like. The Show a context on apply is obviously because the argument has to be shown when showing a partially applied value. For example, TestTaggedFunc.test is a function which adds it's two parameters. *TaggedFunc> TestTaggedFunc.test 1 2 3 I've associated the tag "test" with the function test, so the string "ExtFunc{test}" when read, produces the test function wrapped up in an ExtFunc: *TaggedFunc> let x = read "ExtFunc{test}" :: ExtFunc (Int -> Int -> Int) *TaggedFunc> x ExtFunc{test} (Note that unwrap x is exactly TestTaggedFunc.test) Can partially apply the function and it's still showable: *TaggedFunc> let y = apply x 1 *TaggedFunc> y ExtFunc{test 1} Fully applying the function still shows the parameters: *TaggedFunc> let z = apply y 2 *TaggedFunc> z ExtFunc{test 1 2} But we can unwrap the actual value: *TaggedFunc> unwrap z 3 The main problem it that all the exported functions have to be collected together in the module which has the instance of Read (ExtFunc a), meaning that if a module wants to both export functions for use in ExtFunc and read ExtFuncs, there is a mutual recursion. It also uses Dynamic (to allow all the functions exported from all modules to appear in a single list), so there's potential for types not to match up etc, although I think this isn't much worse than the potential failure of any read due to the type not matching the value in the string. (But I'm not really sure because I've only just discovered the Dynamic module.) Comments appreciated. Regards, Nick

G'day. Quoting nickgrey@softhome.net:
This is the kind of thing I'd like to do, but I can't see how to do it. (Not that I'm claiming it's impossible, I'd played with Haskell on and off for a couple of years now, but I'm a long way from being an expert.)
class StoreableFunc1 a where store :: a -> String unstore :: String -> a apply :: ?? -- What type here??
How about this? class StorableThing thing f | thing -> f where store :: thing -> String unstore :: String -> thing retrieve :: thing -> f Then you can say, for example: data IdFunction = IdFunction instance StorableThing IdFunction (forall a. a -> a) where store _ = "IdFunction" unstore _ = IdFunction retrieve _ = id Note that you're still going to have problems with unstoring these beasts. How to do this is left as an exercise. (Clue: Existential types may help you a lot.) Cheers, Andrew Bromage
participants (5)
-
ajb@spamcop.net
-
dpt@lotus.bostoncoop.net
-
Graham Klyne
-
nickgrey@softhome.net
-
Tomasz Zielonka