Haskell – 40 – tipi di dati algebrici – 7

Enrico Bo, Peherentians Islands

Continuo da qui, copio qui, scrollare fino a “Records”.

Records
By adopting user-defined data types for the graphics interface, we improved type safety and made the interface more expressive than the original line graphics from Spirals, Snowflakes & Trees: Recursion in Pictures. However, there is still room for improvement. For example, the definition of Colour as a product type improved type safety over our original use of a type synonym. However, the type data definition still provides no indication as to which argument of the Colour data constructor represents the, say, blue component of the colour. Moreover, as all colour components are of the same type, confusing them will not lead to a type error, but simply result in an incorrect program. To improve this situation, Haskell offers record syntax for product types.

data Colour
  = Colour { redC      :: Int
           , greenC    :: Int
           , blueC     :: Int
           , opacityC  :: Int
           }
  deriving (Show, Eq)

Here we associate each parameter of the data constructor Colour with a name, which ought to indicate its meaning. Now, quite clearly, the blue component is the one labelled blueC. We call each parameter of a data constructor using record syntax a field and its name a field label. Product types in record syntax are also called structs.

With that definition, we can create values of type Colour either exactly as before (and ignore the field names when applying the data constructor)

red :: Colour
red = Colour 255 0 0 255

or we can use the names to clarify the meaning of each argument:

red :: Colour
red = Colour{redC = 255, opacityC = 255, blueC = 0, greenC = 0}

In the latter case, the fields can be initialised in any order. If we forget a field, the compiler will issue a warning —not a fatal error— and keep the field undefined.

In the same manner in which we can use field names to construct a value of record type, we can use them in pattern matching to identify data constructor arguments. Here we extract the green component of a colour:

greenComponent :: Colour -> Int
greenComponent Color{redC = red, opacityC = alpha, blueC = blue, greenC = green}
  = green

In fact, we can keep this shorter: as we are only interested in the green component, we can ignore the rest and write

greenComponent :: Colour -> Int
greenComponent Color{greenC = green} = green

As extracting of individual fields of a record is a very common operation, Haskell’s record syntax actually provides these projection functions automatically and they carry the same name as the field that they project. In other words, the definition of Colour in record syntax implicitly defines four projection functions

redC     :: Colour -> Int
greenC   :: Colour -> Int
blueC    :: Colour -> Int
opacityC :: Colour -> Int

Given those projections, the definition of greenComponent becomes trivial:

greenComponent :: Colour -> Int
greenComponent = greenC

Although record projection functions are convenient, they come with a caveat. The projection functions are toplevel functions, and hence, all record field names need to be unique in the toplevel name space in which the data type is defined. For example, we cannot have a toplevel user-defined function called greenC in the module defining Colour; otherwise, the name of the projection function would clash with that user-defined function.

In addition to pattern matching using record syntax, we can also still pattern match using the conventional positional syntax, even if the definition of the product type does use record syntax — for example,

isOpaqueColour :: Colour -> Bool
isOpaqueColour (Colour _ _ _ opacity) = opacity == 255

We can also just use the projection function implied by the field name, as in

isOpaqueColour :: Colour -> Bool
isOpaqueColour colour = opacityC colour == 255

Funzioni di proiezione e nomi di campi
When choosing names for record fields, we not only need to consider the potential for name clashes with toplevel functions, but also name classes with other record field names. For example, we might be tempted to use the following definitions:

data Point
  = Point { x :: Float, y :: Float}

data Vector
  = Vector { x :: Float, y :: Float}  -- Error: conflicting definitions of `x` and `y`

However, this leads to conflicting definitions of the two projection functions x and y — for example, x would simultaneously have to be typed as x :: Point -> Float, and as x :: Vector -> Float. As a general rule of good style, we should avoid very short and general field names (even if they do not conflict with other field names) to avoid polluting the toplevel name space. Hence, we choose

data Point
  = Point { xPoint :: Float
          , yPoint :: Float}
  deriving (Show, Eq)

data Vector
  = Vector { xVector :: Float
           , yVector :: Float}
  deriving (Show, Eq)

On the basis of these definitions, we can rewrite the function movePointN, such that it uses the field names:

movePointN :: Float -> Vector -> Point -> Point
movePointN n vector point
  = Point { xPoint = n * xVector vector + xPoint point
          , yPoint = n * yVector vector + yPoint point
          }

However, it is important to note that we do not have to rewrite the function in this manner. The original definition of movePointN is still valid as pattern matching using positional syntax is generally valid for product types defined using record syntax. It is generally wise to choose whichever variant leads to the clearest code.

🤩

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: