Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Haskell - Maybe, Just
Forum - Haskell - Maybe, Just

Avatar
pierotofy (Admin)
Guru^2


Messaggi: 6230
Iscritto: 04/12/2003

Segnala al moderatore
Postato alle 22:34
Lunedì, 26/01/2015
Qualcuno potrebbe darmi una breve spiegazione dell'utilità del tipo (o modificatore?) Maybe?

Codice sorgente - presumibilmente Plain Text

  1. > :t Just "ciao"
  2. 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 il 26/01/2015 alle 23:26


Il mio blog: https://piero.dev
PM Quote
Avatar
lumo (Member)
Expert


Messaggi: 449
Iscritto: 18/04/2010

Segnala al moderatore
Postato alle 1:17
Giovedì, 29/01/2015
Il tipo Maybe è definito circa così come probabilmente sai:

Codice sorgente - presumibilmente Haskell

  1. 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:

Codice sorgente - presumibilmente Haskell

  1. Just :: a -> Maybe a
  2. 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: http://www.infoq.com/presentations/Null-References-The-Bil ..., 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
Codice sorgente - presumibilmente Haskell

  1. somma :: Int -> Int -> Int
  2. somma x y = x + y


Purtroppo i numeri provengono da un parser che gestisce l'opportunità di errore attraverso Maybe:
Codice sorgente - presumibilmente Haskell

  1. parseNumber :: String -> Maybe Int


Se volessimo sommare i due numeri dovremmo fare
Codice sorgente - presumibilmente VB.NET

  1. let n1 = parseNumber input1
  2.     n2 = parseNumber input2
  3. in
  4.     case n1 of
  5.         Just x -> case n2 of
  6.             Just y -> Just (somma x y)
  7.             Nothing -> Nothing
  8.         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:
Codice sorgente - presumibilmente VB.NET

  1. applyMaybeBinary :: (Maybe a -> Maybe b -> Maybe c) -> Maybe a -> Maybe b
  2. applyMaybeBinary fun Nothing _ = Nothing
  3. applyMaybeBinary fun _ Nothing = Nothing
  4. applyMaybeBinary fun (Just x) (Just y) = fun x y


Ora potremmo riscrivere l'espressione di prima come:
Codice sorgente - presumibilmente Haskell

  1. let n1 = parseNumber input1
  2.     n2 = parseNumber input2
  3. in
  4.     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:
http://hackage.haskell.org/package/base-4.7.0.2/docs/Data- ...
http://hackage.haskell.org/package/base-4.7.0.2/docs/Contr ...

Un programmatore haskell riscriverebbe il codice sopra così:
Codice sorgente - presumibilmente Haskell

  1. import Control.Applicative
  2. ...
  3. let n1 = parseNumber input1
  4.     n2 = parseNumber input2
  5. in
  6.     (liftA2 (+)) n1 n2


Oppure più idiomaticamente
Codice sorgente - presumibilmente Plain Text

  1. (+) <$> n1 <*> n2


Ultima modifica effettuata da lumo il 29/01/2015 alle 1:26
PM Quote
Avatar
pierotofy (Admin)
Guru^2


Messaggi: 6230
Iscritto: 04/12/2003

Segnala al moderatore
Postato alle 3:11
Giovedì, 29/01/2015
Grazie per la spiegazione!

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

Codice sorgente - presumibilmente Haskell

  1. 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: https://piero.dev
PM Quote
Avatar
lumo (Member)
Expert


Messaggi: 449
Iscritto: 18/04/2010

Segnala al moderatore
Postato alle 17:13
Giovedì, 29/01/2015
Testo quotato

Postato originariamente da pierotofy:

Grazie per la spiegazione!

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

Codice sorgente - presumibilmente Haskell

  1. 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.

PM Quote
Avatar
ZioCrocifisso (Member)
Pro


Messaggi: 135
Iscritto: 06/03/2013

Segnala al moderatore
Postato alle 19:21
Giovedì, 29/01/2015
Testo quotato

Postato originariamente da pierotofy:

Grazie per la spiegazione!

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

Codice sorgente - presumibilmente Haskell

  1. 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.

PM Quote