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
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe