Haskell – 11 – elementi fondamentali – 5

Continuo da qui, copio qui, scrollare fino a “Lists versus tuples”.

Confronto liste – tuples
As lists and tuples are often confused, let us summarise the differences between them. Tuples have the following properties:

  • Fixed size, i.e., fixed number of components: the pair (1, 2) :: (Int, Int) and the triple (1, 2, 3) :: (Int, Int, Int) have different types.
  • Components may be of different type: (5, Hello) makes perfect sense.

In contrast, the following are the properties of lists:

  • Variable size, i.e., number of components may vary: [1, 2] :: [Int] and [1, 2, 3] :: [Int] have the same types.
  • Components must be of the same type: [5, Hello] is incorrect.

Stringhe come liste
Strings are in fact a particular form of lists in Haskell, which are defined as follows in the Prelude:

type String = [Char]

Hence, list operations work on strings. "Hello" !! 1   ⇒   'e' In fact, in Haskell, "Hello" is exactly the same as ['H', 'e', 'l', 'l', 'o']. This is very convenient, as we will see that there are many powerful list processing operations and these are directly available for string manipulation.

Hence, if we ask the Haskell system for the type of an expression that produces a list, it may report the type as either String or as [Char]. As these two types have been deemed equal by the above type definition, they may be used interchangeably

Funzioni parziali
Some list functions, such as length, may be applied to any list and always produce a meaningful result. In contrast, some other functions, such as head and tail, fail for some lists. For example, if we apply head to an empty list, we get a runtime error or runtime exception.

Prelude> head []
*** Exception: Prelude.head: empty list

The function head is defined by pattern matching using the same symmetry between list construction and list pattern matching as we discussed previously for tuples — i.e., it matches on the cons-operator (:):

head :: [a] -> a
head (x:xs) = x

When head gets applied to the empty list, there is simply no function equation that matches this case. We call such functions partial functions. Their definition ignores —or is invalid for— some of the input values admitted by the function’s type signature. We discussed that types represent sets of related values; hence, partial functions are functions that are only defined for a subset of the values included in their argument types. The opposite are total functions. Total functions, such as length, are defined for all values included in the sets represented by their argument types.

Partial functions are a common source of programming mistakes. Hence, good style dictates that the documentation of a partial function (e.g., as a comment preceding the function definition) includes a statement specifying the range of admissible input values (and specifies what happens if the function is incorrectly called with an argument that is outside of this range).

The Haskell Prelude includes the function

error :: String -> a

that always results in a runtime error using its argument as the error message — that is, we have

Prelude> error "encountered a fatal error"
*** Exception: encountered a fatal error

We can use error to customise the error message of a partial function. For example, the complete definition of head in the Haskell Prelude reads as follows:

head :: [a] -> a
head (x:_) = x
head []    = error "Prelude.head: empty list"

This is better than the default error message generated by the Haskell compiler, which includes the location of the failing function definition, but doesn’t specify the argument value that led to the failure.

Note the use of the underscore _ as the second argument of the cons-operator in the first equation. It represents a pattern matching position whose value is not used in the body of the function definition. It is better style than using a regular variable name, such as xs, as it immediately signals that the corresponding value goes unused. Instead of a plain underscore, we can also use variable names starting with an underscore character (here, for example _xs) to indicate that the value is not used.

Layout
Unlike in many other programming languages, formatting matters in Haskell. In other words, the correct use of indentation and newlines is crucial to avoid errors. This allows the language to do away with some of the noise that is introduced by some other languages to disambiguate the input — in particular, Haskell programs don’t need curly braces or semicolon to delimit blocks and statements.

Compare

foo x 
  = a + b
  where
    a = 1 + x
    b = 2

to

foo x 
  = a + b
  where
    a = 1 + x
b = 2

Both are legal programs. However, in the first one, the definition of b is part of the where binding and therefore local to foo whereas in the second program, the use of b is not restricted to foo.

An example of proper layout is the function distance that we discussed earlier:

distance :: ColourPoint -> ColourPoint -> Float
distance (x1, y1, colour1) (x2, y2, colour2) 
  = sqrt (fromIntegral (dx * dx + dy * dy))
  where
    dx = x2 - x1
    dy = y2 - y1

There are three layout rules that we have to follow to get syntactically correct programs:

  • All program code that belongs to a function definition has to be further to the right than the first character of that definition (i.e., the first character of the function name). In the case of distance, all code has to be further to the right than the column in which the character d of the function name distance is located.
  • Similarly, all code of a local definition in a where clause must be further to the right than the first character of the name of the variable defined by the binding.
  • All definitions within a where clause must be aligned — e.g., above the definitions of dx and dy start in the same column.

Alternatively, we can explicitly group the bindings of a where clause using curly braces, and separate the individual bindings by semicolons:

distance (x1, y1, colour1) (x2, y2, colour2) 
  = sqrt (fromIntegral (dx * dx + dy * dy))  
  where {dx = x2 - x1; dy = y2 - y1}

Inside the curly braces, we can format the code anyway we like, because the compiler can still figure out what belongs where. Nevertheless, while the following program is legal

distance (x1, y1, colour1) (x2, y2, colour2) 
  = sqrt (fromIntegral (dx * dx + dy * dy)) where {dx 
= x2 - 
x1; dy = y2 - y1}

it is hard to read and clearly pretty bad style. Seasoned Haskell programmers who prefer explicit bracketing align the braces and the semicolons at the beginning of each line (which looks rather strange to, say, a C-programmer, who is used to having semicolons at the end of the line):

distance (x1, y1, colour1) (x2, y2, colour2) 
  = sqrt (fromIntegral (dx * dx + dy * dy))  
  where 
    { dx = x2 - x1
    ; dy = y2 - y1
    }

Qui (di là) volendo c’è il video riassuntivo.

🤢

Annunci
Post a comment or leave a trackback: Trackback URL.

Trackbacks

Rispondi

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

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. 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 )

Google+ photo

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

Connessione a %s...

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