A function that has a finite but arbitrary number of heterogeneous elements

I am working on a library to study theoretical teaching of the game. In this setup, N agents combine and interact with the environment. Each agent receives an interaction model. The single agent model depends on his opponents N-1 . I wrote code defining this model for agents 1 agent and 2 , and am trying to generalize it. Here is the part of the code that I have:

 data System wxashh' = System { signaling :: SignalingWXAS wxas , dynamic :: DynamicXAS xas , strategy :: MockupStrategy xashh' } data JointState wxashh' = JointState { worldState :: w , state :: x , mockupState :: MockupState ashh' } systemToMockup :: ( Bounded w, Ix w, Bounded x, Ix x , Bounded a, Ix a, Bounded s, Ix s , Bounded (ha), Ix (ha), Bounded (h' s), Ix (h' s) , History h, History h' ) => System wxashh' -> Mockup ashh' systemToMockup syst = mock where mock z = norm $ statDist >>=? condit z >>= computeStatesAndAction >>= sig >>=$ extractSignal statDist = ergodicStationary $ transition syst condit z = just z . mockupState sig = uncurryN $ signaling syst strat = strategy syst computeStatesAndAction joint = do let w = worldState joint let x = state joint a <- strat x (mockupState joint) return (w, x, a) extractSignal (_, s) = s 

and

 data System2 w x1 a1 s1 h1 h1' x2 a2 s2 h2 h2' = System2 { signaling :: SignalingWXAS2 w x1 a1 s1 x2 a2 s2 , dynamic1 :: DynamicXAS x1 a1 s1 , dynamic2 :: DynamicXAS x2 a2 s2 , strategy1 :: MockupStrategy x1 a1 s1 h1 h1' , strategy2 :: MockupStrategy x2 a2 s2 h2 h2' } data JointState2 w x1 a1 s1 h1 h1' x2 a2 s2 h2 h2' = JointState2 { worldState :: w , state1 :: x1 , mockupState1 :: MockupState a1 s1 h1 h1' , state2 :: x2 , mockupState2 :: MockupState a2 s2 h2 h2' } systemToMockups2 syst = (mock1, mock2) where mock1 z1 = norm $ statDist >>=? condit1 z1 >>= computeStatesAndActions >>= sig >>=$ extractSignal1 mock2 z2 = norm $ statDist >>=? condit2 z2 >>= computeStatesAndActions >>= sig >>=$ extractSignal2 statDist = ergodicStationary $ transition2 syst condit1 z1 = just z1 . mockupState1 condit2 z2 = just z2 . mockupState2 sig = uncurryN $ signaling syst strat1 = strategy1 syst strat2 = strategy2 syst computeStatesAndActions joint = do let w = worldState joint let x1 = state1 joint let x2 = state2 joint a1 <- strat1 x1 (mockupState1 joint) a2 <- strat2 x2 (mockupState2 joint) return (w, x1, a1, x2, a2) extractSignal1 (_, s, _) = s extractSignal2 (_, _, s) = s 

I am after defining a function for systemToMockupN , which can accommodate any finite number of agents.

Agents are heterogeneous, so using lists is not possible. I can’t use tuples because I don’t know the size in advance. I tried using curryN , uncurryN , etc., but could not perform one operation for each element of the tuple. I tried to create a variational function like printf without success.

I know that I can use the haskell template, but I am wondering if there is a nicer solution that I am missing. Any pointer to some code dealing with a finite but arbitrary number of heterogeneous elements would be greatly appreciated.

+4
source share
5 answers

Generic Algebraic Data Types (GADT).

This allows you to combine in a single row a finite number of different heterogeneous data types and is a modern way of creating existential types. They sit somewhere between data Agent = AH Human | AP Plant | .... data Agent = AH Human | AP Plant | .... data Agent = AH Human | AP Plant | .... and the HList approach. You can make all your incredibly heterogeneous agents instances of some type, and then combine them into AgentGADT . Make sure that your class has everything that you will ever want to do with it in the Agent, because it is difficult to get data back from GADT without a function with an explicit type; will you need getHumans [AgentGADT] -> [Human] ? or updateHumans :: (Human->Human) -> [AgentGADT] -> [AgentGADT] ? This would be simpler with the usual abstract data type in my other post.

Pros: You can have [AgentGADT] and work evenly with class functions, while writing weird and wonderfully parameterized data types. Minus Points - It’s hard to get your agent’s data after you log in.

My favorite online intro was GADT for layouts .

+2
source

Do not go heterogeneous. It is not worth it. It is worth finding the best way. Here is one way to avoid this. (There are other ways.) There can be an arbitrary number of agents, but, of course, there is no arbitrary number of agent types. (Do types need to be so parameterized? Your community is too expensive for you, I'm afraid.

  class AnAgent a where liveABit :: World -> a -> (World,a) ... ... data NuclearPlant = .... data CoalPlant = .... data WidFarm = ..... data DataCenter = ..... data EnvFriendly = ..... data PetrolHead = ..... 

Group them a bit for general treatment using pattern matching, if convenient:

  data Plant = PN NuclearPlant | PC CoalPlant | PW WindFarm data Human = HEF EnvFriendly | HPE PetrolHead data Agent = AP Plant | AH Human | AD DataCenter 

General / heterogeneous treatment within groups / groups:

  bump :: Agent -> Agent bump (Human h) = Human (breakleg h) bump a = a 

Then you can identify all the agents you need and then put them in [Agent] and define a good eachLiveABit :: World -> [Agent] -> (World,[Agent]) to update the world and its agents. You can create AnAgent instances for individual types or groups of agents and create them before Agent (or, possibly, even without a type class, use regular functions evenly and simply).

This will follow the software transformation (Classes + Interesting Type System Features) -> (Types and Higher Order Functions) , which feels emotionally, like your dummy, but makes work very difficult.

+3
source

heterogeneous. I do not think you should do this, but you asked.

You can use the existing library for heterogeneous lists, such as HList . In any case, it secretly uses Template Haskell, but you do not need to get your hands dirty as if you did it yourself.

Languages ​​with dynamic typing have all kinds of problems due to the need for casting, etc. HList is type safe, but it’s not easy for me to write code that works well and doesn’t get buggy. You get an edge over tuples because you don’t need to change the type, and matching updates by elements should be simpler, but that's still not very good.

+3
source

You can try to solve this using type classes.
Here are a few pseudo codes:

 data Mockup = Mockup1 <return type of systemToMockup> | Mockup2 <return type of systemToMockups2> class SystemToMockup a where systemToMockup :: a -> Mockup instance SystemToMockup (System1 ...) where <implementation of systemToMockup> instance SystemToMockup (System2 ...) where <implementation of systemToMockups2> 

This is a fairly limited solution, and I doubt it will work for you, as your application seems rather complicated. In general, the approach of K. A. McCann is much better.

0
source

Update:. One thing this strategy can't handle is polymorphic return types. In addition, parameters that have different types depending on the main type add a rather complicated task.

This was mentioned in the comments, but one of the best ways to make a “heterogeneous collection” is to decide what operations you can actually perform for each element of the collection (since you can only do a limited number of things on them, even if your language printed duck) and keep records of these operations. Example:

 data Thing1 = ... data Thing2 = ... data AThing = AThing { op1 :: ... op2 :: ... } class IsAThing a where mkAThing :: a -> AThing instance IsAThing Thing1 where mkAThing a = AThing { op1 = op1_for_thing1 a, op2 = op2_for_thig1 a } 

Then, to call op1 on any IsAThing:

 op1 (mkAThing a) <...other args...> 

Although in your case you need an AThing list:

 [mkAThing a2, mkAThing a2, ...] 

Then on any element:

 op1 <element of that list> <...other args...> 
0
source

Source: https://habr.com/ru/post/1435293/


All Articles