Haskell – 31 – funzioni di ordine superiore – 8

Continuo da qui, copio qui, scrollare fino a “Higher-order Functions Provide Flexibility”.

Le funzioni di ordine superiore forniscono flessibilità
We have seen that the use of higher-order functions can increase code reuse and lead to concise, more readable code. We have yet to discuss that, in addition, higher-order functions can provide flexibility. To illustrate this benefit, let us return to the Pythagorean trees from Spirals, Snowflakes & Trees: Recursion in Pictures [video che trovate sul tutorial che sto seguendo, qui]. The exercises in that chapter focus on extending the given code, such that multi-coloured trees can be produced (as opposed to creating a single path that represents the entire tree, which only let’s us display the tree in one single colour). We might implement this as follows:

fade :: Colour -> Colour
fade col@(redC, greenC, blueC, opacityC)
  | opacityC == 0 = col
  | otherwise     = (redC, greenC, blueC, opacityC - 20)

colouredFTree :: Int -> Float -> Colour -> Line -> Picture
colouredFTree n factor colour line = fT n colour line 
  where
    fT 0 colour line = [(colour, [fst line, snd line])]  
    fT n colour line = [(colour,[p2, p3])]  
                       ++ [(colour,[p4, p1])] 
                       ++ fT (n-1) colour' (p5, p3) 
                       ++ fT (n-1) colour' (p4, p5)
      where 
        colour'         = fade colour
        [p1,p2,p3,p4,_] = polygon 4 line
        (_, p5)         = rotateLine (factor * pi)
                            $ (\(x,y) -> (y,x)) 
                            $ scaleLine 0.5 (p3, p4)

Using this function in

drawPicture 3 (colouredFTree 14 0.55 red line3)

produces the following picture, where we incrementally reduce the opacity of the colour as the recursion progresses.

Now, there are a lot of other, more interesting colour effects than simply reducing the opacity of a colour. One option is to bleach the initial colour:

bleach :: Colour -> Colour
bleach (redC, greenC, blueC, opacityC)
  = (min 255 (redC + 18), 
     min 255 (greenC + 18), 
     min 255 (blueC + 18),
     opacityC)

colouredFTree :: Int -> Float -> Colour -> Line -> Picture
colouredFTree n factor colour line = fT n colour line 
  where
    fT 0 colour line = [(colour, [fst line, snd line])]  
    fT n colour line = [(colour,[p2, p3])]  
                       ++ [(colour,[p4, p1])] 
                       ++ fT (n-1) colour' (p5, p3) 
                       ++ fT (n-1) colour' (p4, p5)
      where 
        colour'         = bleach colour
        [p1,p2,p3,p4,_] = polygon 4 line
        (_, p5)         = rotateLine (factor * pi)
                            $ (\(x, y) -> (y, x)) 
                            $ scaleLine 0.5 (p3, p4)

With this definition, we can generate the following pictures, by using red, green and black, respectively, as initial colours:

Clearly, there are countless other possibilities, and we definitely don’t want to rewrite the code, or define a new variant of the existing code, every time we try something new. Instead of committing to a fixed method to calculate the colour for each recursive step, we can pass a function which takes care of this decision as higher-order parameter to the tree construction function.

What should the type of that function parameter be? In the two examples above, we used a function of type Colour -> Colour, so this seems a reasonable choice. If we want to avoid having to pass an initial colour in addition to the function, we can alternatively model it as function from recursion depth to colour of type Int -> Colour.

colouredFTree :: Int -> Float -> (Int -> Colour) -> Line -> Picture
colouredFTree n factor colourFun line = fT n line 
  where
    fT 0  line = [(colourFun 0, [fst line, snd line])]  
    fT n  line = [(colourFun n, [p2, p3])]  
                       ++ [(colourFun n,[p4, p1])] 
                       ++ fT (n-1) (p5, p3) 
                       ++ fT (n-1) (p4, p5)
      where 
        [p1,p2,p3,p4,_] = polygon 4 line
        (_, p5)         = rotateLine (factor * pi)
                            $ (\(x, y) -> (y, x)) 
                            $ scaleLine 0.5 (p3, p4)

For example, by passing these functions as arguments to colouredFTree:

magentaToWhite, toBlue1, toBlue2 :: Int -> Colour
magentaToWhite n = (127 + (18 - n) * 7, (18 - n) * 15, 255, 255)
toBlue1 n        = (255 - (16 - n) * 13, 255 - (16 - n) * 13, 214, 255)
toBlue2 n        = (51 + n * 8, 255 + 14 * n, 255, 255)

we generate the following images.

Another way to create more interesting, organically looking trees is to vary factor argument of colouredFTree, which determines the ratio between the size of the left and right subtree in each recursive step. As with the colour, we replace the constant value by a function that characterised the change of the factor in dependence on the recursion depth:

colouredFTree :: Int -> (Int -> Float) -> (Int -> Colour) -> Line -> Picture
colouredFTree n factorFun colourFun line = fT n line 
  where
    fT 0  line = [(colourFun 0, [fst line, snd line])]  
    fT n  line = [(colourFun n, [p2, p3])]  
                       ++ [(colourFun n,[p4, p1])] 
                       ++ fT (n-1) (p5, p3) 
                       ++ fT (n-1) (p4, p5)
      where 
        [p1,p2,p3,p4,_] = polygon 4 line
        (_, p5)         = rotateLine ((factorFun n) * pi)
                            $ (\(x, y) -> (y, x)) 
                            $ scaleLine 0.5 (p3, p4)

This enables us to generate a rather varied collection of fractals trees with the one definition of colouredFTree using

toggleFactor2 factor n = if (n `mod` 2) == 0 then factor else (1 - factor)
toggleFactor5 factor n = if (n `mod` 5) == 0 then factor else (1 - factor)
shiftFactor factor n   = factor + (fromIntegral (16 - n)) * 0.025

as function parameters partially applied as toggleFactor2 0.7, toggleFactor5 0.7, and shiftFactor 0.5, all with an iteration depth of 16.

What if we don’t want to vary one of the two function parameters as the recursion progresses (to get some of our initial fractal trees)? In this case, we simply pass a constant function as an argument — i.e., a function whose result is independent of its argument:

colouredFTree n (\_ -> 0.5) (\_ -> red) line

Constant functions are often useful; hence, the Haskell Prelude provides a combinator to construct them (without an explicit lambda abstraction):

const :: a -> b -> a
const x _ = x

This function is always used with partial application; for example, we can rephrase the previous invocation of colouredFTree as

colouredFTree n (const 0.5) (const red) line

While the second version is not much more concise, it is considered better style.

Uhmmmm… esempio complesso, devo studiarmelo per capire cosa succede; forse era meglio uno più semplice dove si evidenziava le parti varianti.

🤩

Posta un commento o usa questo indirizzo per il trackback.

Trackback

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: