Progettare nuovi tipi di dato e Liste Polimorfe

Durante lo sviluppo di un un programma complesso può capitare di dover differenziare alcune variabili (appartenenti al medesimo tipo) sulla base del significato che si attribuisce ad esse. Consideriamo alcuni scenari d’uso, stiamo progettando una rubrica telefonica digitale e siamo interessati a differenziare l’espressione incaricata di rappresentare il nome dell’utente da quella incaricata di rappresentare il suo l’indirizzo email.

Oppure, immaginiamo di dover progettare il sistema di controllo di un missile, sarebbe utile poter differenziare la sua velocità orizzontale dalla sua velocità verticale ed impedire l’esecuzione di alcune operazioni (concettualmente sbagliate) tra le due tipologia di dato, pensiamo ad esempio alla somma tra la velocità orizzontale e la velocità verticolare (non avete idea di quanti errori abbiamo causato operazioni di questo tipo). Quello che possiamo fare in F# è decidere di costruire un alias da utilizzare per identificare intuitivamente una certa tipologia di dato:

type EmailAddress = string;;
type State        = string;;
type Zip          = string;;

I nuovi tipi così definiti (“EmailAddress”, “State”, “Zip”) sono però dei semplici alias del tipo nativo “string”, essi verranno sostituiti durante il processo di compilazione e non saranno quindi presenti durante l’esecuzione del programma. Dal momento che non vi è encapsulation un valore di tipo “EmailAddress” corrisponderà ad un valore di tipo “State”, questa soluzione quindi non risolve completamente il nostro problema. Durante la stesura del nostro programma saremo più consapevoli del significato di certe espressioni, tuttavia non ci sarà impedito di svolgere alcune operazioni potenzialmente pericolose (come la sopra-citata somma di velocità)

Consideriamo il seguente esempio:

type State = string;;
type Zip   = string;;

let inLombardia (z : Zip) = (z.Substring(0, 2) = "20");;

let cap1   = ("20000" : Zip);;
let cap2   = ("30000" : Zip);;
let state1 = ("Italia" : State);;
let state2 = ("2000" : State);;

inLombardia cap1;;
// val it : bool = true

inLombardia cap2;;
// val it : bool = false

inLombardia state1;;
// val it : bool = false

inLombardia state2;;
// val it : bool = true

Non abbiamo assolutamente raggiunto il nostro scopo. Quello che dobbiamo fare in questi casi è definire una nuova tipologia di dato, facendo in modo che questa venga mantenuta durante la compilazione (o l’interpretazione) del programma, nel linguaggio F# questa operazione può ad esempio essere svolta in questo modo:

module EmailAddress = 
    type T = EmailAddress of string

    // wrap
    let create (s:string) = 
        if System.Text.RegularExpressions.Regex.IsMatch(s, @"^\S+@\S+\.\S+$") then
            EmailAddress s
        else
            failwith "L'indirizzo email non è valido"
    
    // unwrap
    let value (EmailAddress e) = e

module State = 
    type T = State of string

    // wrap
    let create (s:string) = State s
    
    // unwrap
    let value (State e) = e

module Zip = 
    type T = Zip of string

    // wrap
    let create (s:string) = 
        if System.Text.RegularExpressions.Regex.IsMatch(s, @"^\d{5}$") then
            Zip s
        else
            failwith "Il CAP non è valido"
    
    // unwrap
    let value (Zip e) = e

(****************************************************************************)
(*************************** Corpo del programma ****************************)
(****************************************************************************)

let inLombardia z = z |> Zip.value |> (function z -> z.Substring(0, 2) = "20");;
 
let cap1   = Zip.create "20000";;
let cap2   = Zip.create "30000";;
let state1 = State.create "Italia";;
let state2 = State.create "2000";;
 
inLombardia cap1;;
// val it : bool = true
 
inLombardia cap2;;
// val it : bool = false
 
inLombardia state1;;
// error FS0001: Il tipo previsto di quest'espressione è Zip.T tuttavia il tipo risulta essere State.T

inLombardia state2;;
// error FS0001: Il tipo previsto di quest'espressione è Zip.T tuttavia il tipo risulta essere State.T

Liste Polimorfe

Nel capitolo precedente abbiamo visto come sia possibile raggruppare elementi appartenenti a diverse tipologie di dato all’interno di un nuovo elemento da noi definito, estendiamo ora tale concetto fino a giungere alla definizione di una lista di dati polimorfa, i cui elementi appartengono a tipologie di dato differenti. Definiamo come esempio una nuova lista polimorfa denominata “PList”:

type 'a PList =
     |PNull
     |PElm of 'a * 'a PList;;

La lista appena definita è basata su un tipo di dato generico, infatti viene definita come ‘a PList, essa potrà contenere l’elemento “PNull”, oppure un elemento di tipo “PElm”. L’elemento “Pelm” è a sua volta costituito da una tupla contenente un valore generico ‘a e un valore di tipo ‘a PList (una nuova lista). Vediamo alcuni esempi:

let Pl1 = PElm (3, PElm (4, PElm ((1 + 4), PNull)));;
// val Pl1 : int PList = PElm (3, PElm (4, PElm (5, PNull)))

let Pl2 = PElm (3 < 5, PElm (true, PElm (10 > 20, PNull)));;
// val Pl2 : bool PList = PElm (true, PElm (true, PElm (false, PNull)))

Le liste polimorfe sono largamente utilizzate in programmazione, ad esempio nella realizzazione di strutture ad albero o a grafo (Alberi binari e alberi ricerca in F#).