Oppure

Loading
26/01/15 21:34
pierotofy
Qualcuno potrebbe darmi una breve spiegazione dell'utilità del tipo (o modificatore?) Maybe?

> :t Just "ciao"
Just "ciao" :: Maybe [Char]


In quale contesto pratico viene usato?

Edit: ok, capisco che Maybe definisce un tipo "nullable" (un tipo che può ritornare Nothing). Ma Just?
Ultima modifica effettuata da pierotofy 26/01/15 22:26
Il mio blog: piero.dev
29/01/15 0:17
lumo
Il tipo Maybe è definito circa così come probabilmente sai:

data Maybe a = Just a | Nothing deriving (blabla)


Quelli a destra dell'uguale sono i costruttori per il tipo Maybe. E' comune che a volte i costruttori non abbiano lo stesso nome del tipo che "creano", in questo caso ha senso.
Di fatto un costruttore per Maybe a altro non è che una funzione con tipo (parametri...) -> Maybe a.
Il compilatore da quelle due indicazioni per il costruttore crea due funzioni cone questo tipo:

Just :: a -> Maybe a
Nothing :: Maybe a


A prima vista il tipo Maybe potrebbe sembrare in tutto e per tutto un tipo nullable, come dici tu. (A questo proposito, anche se off topic: infoq.com/presentations/…, lol)

In Haskell però non si fa quasi mai un check esplicito su un dato di tipo Maybe a per vedere se sia Nothing o qualcosa, altrimenti il codice diventerebbe presto ingestibile.
Un esempio semplice: supponi di avere
somma :: Int -> Int -> Int
somma x y = x + y

Purtroppo i numeri provengono da un parser che gestisce l'opportunità di errore attraverso Maybe:
parseNumber :: String -> Maybe Int

Se volessimo sommare i due numeri dovremmo fare
let n1 = parseNumber input1
    n2 = parseNumber input2
in
    case n1 of
        Just x -> case n2 of
            Just y -> Just (somma x y)
            Nothing -> Nothing
        Nothing -> Nothing

Tutta questa espressione ha come tipo Maybe Int (questo è quello che si intende quando si dice che il typesystem di haskell mantiene la distinzione tra codice puro e codice con side effects: non è più possibile estrarre il tipo "Int" da "Maybe Int";).
Tuttavia siccome è alquanto brutto, conviene definire una funzione che faccia un po' di pulizia:
applyMaybeBinary :: (Maybe a -> Maybe b -> Maybe c) -> Maybe a -> Maybe b
applyMaybeBinary fun Nothing _ = Nothing
applyMaybeBinary fun _ Nothing = Nothing
applyMaybeBinary fun (Just x) (Just y) = fun x y

Ora potremmo riscrivere l'espressione di prima come:
let n1 = parseNumber input1
    n2 = parseNumber input2
in
    applyMaybeBinary somma n1 n2

La funzione applyMaybeBinary ci permette di usare qualsiasi generica funzione binaria con parametri di tipo Maybe. Funzioni higher order di questo tipo sono molto comuni in haskell e questa operazione è solitamente detta di lifting, perché fai lavorare la funzione su tipi "superiori".
Fortunatamente tutto ciò è integrato nella libreria standard di haskell:
hackage.haskell.org/package/base-4.7.0.2/docs/…
hackage.haskell.org/package/base-4.7.0.2/docs/…

Un programmatore haskell riscriverebbe il codice sopra così:
import Control.Applicative
...
let n1 = parseNumber input1
    n2 = parseNumber input2
in
    (liftA2 (+)) n1 n2

Oppure più idiomaticamente
(+) <$> n1 <*> n2
Ultima modifica effettuata da lumo 29/01/15 0:26
aaa
29/01/15 2:11
pierotofy
Grazie per la spiegazione!

Più precisamente, ero un pò confuso dal fatto che non si potesse fare qualcosa del genere:

data Maybe a = a | Nothing


La grammatica del linguaggio richiede che ci sia un 'data constructor' (Just). Leggerò alcune volte quello che hai scritto per assicurarmi di aver capito.
Il mio blog: piero.dev
29/01/15 16:13
lumo
Postato originariamente da pierotofy:

Grazie per la spiegazione!

Più precisamente, ero un pò confuso dal fatto che non si potesse fare qualcosa del genere:

data Maybe a = a | Nothing


La grammatica del linguaggio richiede che ci sia un 'data constructor' (Just). Leggerò alcune volte quello che hai scritto per assicurarmi di aver capito.

Il motivo per cui quello non è possibile è più filosofico(e poi si riflette nella sintassi): in Haskell non esiste il subtyping.
null in un linguaggio OOP normalmente può essere il valore di qualsiasi variabile, perché se Object è nullable allora tutte le sue sottoclassi lo sono.
In haskell di solito invece di progettare con l'ereditarietà e il subtyping si usa il data polymorphism, le informazioni implicite che vengono date dalla gerarchia si trasferiscono esplicitamente nel polimorfismo dei tipi.
Da quello che so Scala combina questi due approci, ma a quanto dice Simon Peyton-Jones (il creatore di Haskell) questo rende il type system molto complesso.
aaa
29/01/15 18:21
ZioCrocifisso
Postato originariamente da pierotofy:

Grazie per la spiegazione!

Più precisamente, ero un pò confuso dal fatto che non si potesse fare qualcosa del genere:

data Maybe a = a | Nothing


La grammatica del linguaggio richiede che ci sia un 'data constructor' (Just). Leggerò alcune volte quello che hai scritto per assicurarmi di aver capito.

I "data" sono tagged union, e il costruttore rappresenta il tag. Dev'esserci sempre, altrimenti non si saprebbe a quale costruttore ci si sta riferendo. I Maybe possono essere usati come nullable, ma Nothing non è assolutamente un null, è un valore come tutti gli altri. Negli altri linguaggi è possibile fare una distinzione tra il valore esistente e il null perché il null è un valore che i riferimenti a un dato esistente non possono mai assumere. La confusione che quella struttura genererebbe si può notare se si crea un valore di tipo Maybe (Maybe Int). Se il valore fosse "Nothing", si tratterebbe del costruttore del primo Maybe o di quello più interno? Con il data corretto, il Nothing del Maybe esterno sarebbe "Nothing", quello interno "Just Nothing". Nel tuo caso non sarebbe possibile distinguerli. In Haskell esistono effettivamente i puntatori e quelli nulli, ma vengono usati soltanto per interagire con librerie scritte in altri linguaggi, per esempio C, perché sono insicuri e portano a bug.
aaa