[Haskell-cafe] I just don't get it (data structures and OO)
Tillmann Rendel
rendel at rbg.informatik.tu-darmstadt.de
Sun Jun 3 07:27:21 EDT 2007
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
More information about the Haskell-Cafe
mailing list