[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