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

Reply via email to