
Hello, Phlex wrote:
changePlanetAge universe galaxy planet age = ...lots of code, returning a new universe And the same code for all functions updating any of the properties of my planet ... And the same code for all functions updating properties of a country on this planet...
In functional programming, problems of the kind "and the same code for all functions doing something related" are most often solved by introducing higher order functions. Let's try to decompose the problem as follows: (1) change an attribute (2) change a planet by changing one of it's attributes (3) change a galaxy by changing one of it's planets (4) change an universe by changing one of it's galaxies. (1) is done by functions of type (Attribute -> Attribute). We will construct them on the fly. For (2), we need functions for each attribute of a planet. type Name = String type Age = Integer data Planet = Planet Name Age -- lift name changer to planet changer changeName :: (Name -> Name) -> Planet -> Planet changeName f (Planet n a) = Planet (f n) a -- lift age changer to planet changer changeAge :: (Age -> Age) -> Planet -> Planet changeAge f (Planet n a) = Planet n (f a) we need one of these functions for each attribute of each object. they correspond to setter-Methods in oop. For (3), we have to select one of the planets in a galaxy to be changed. Let's assume integer indices for all planets. type Galaxy = [Planet] -- lift planet changer to galaxy changer changePlanet :: Integer -> (Planet -> Planet) -> Galaxy -> Galaxy changePlanet 0 f (p:ps) = f p : ps changePlanet n f (p:ps) = p : changePlanet (pred n) f ps For (4), we have to select one of the galaxies in a universe to be changed. Let's assume integer indices again. type Universe = [Galaxy] -- lift galaxy changer to universe changer changeGalaxy :: Integer -> (Galaxy -> Galaxy) -> Universe -> Universe changeGalaxy 0 f (g:gs) = f g : gs changeGalaxy n f (g:gs) = g : changeGalaxy (pred n) f gs Oups, that's the same as (3), up to renaming of types and variables. Let's refactor it to -- lift element changer to list changer changeElement :: Integer -> (a -> a) -> [a] -> [a] changeElement f 0 (x:xs) = f x : xs changeElement f n (x:xs) = x : changeListElement f (pred n) xs -- provide nicer names changePlanet = changeElement changeGalaxy = changeElement Let's see how we can use this: To set the name of the second planet of the third galaxy to "earth": (changeGalaxy 2 $ changePlanet 1 $ changeName $ const "earth") univ To increase the age of the same planet by 1: (changeGalaxy 2 $ changePlanet 1 $ changeAge $ succ) univ Using map instead of changeElement, we can change all galaxies or planets at once: -- provide nicer names changeGalaxies = map changePlanets = map To set the name of the first planet in all galaxies to "first": (changeGalaxies $ changePlanet 0 $ changeName $ const "first") univ To increase the age of all planets by one: (changeGalaxies $ changePlanets $ changeAge $ succ) univ A possible next step is to use typeclasses as supposed by apfelmus to access elements of different structures in a uniform way. Tillmann