[Haskell] Dubbio IO...
Ciao 
Iniziamo con un pezzo di un codice più vasto e scorporato per quello che mi serve sapere.
In realtà in questo caso non è cruciale sapere cose fanno le funzioni, piuttosto sapere i tipi delle cose.
Prendiamo un [tt]main[/tt] così
Mi chiedo che fine faccia quel [tt]getCfgs :: IO (Maybe2 String)[/tt] richiamato da definizione dietro le quinte ben quattro volte consecutive. Mi sto riferendo a questo pezzo su Real World Haskell
Cosa fa il calcolatore? È un [tt]IO qualcosa[/tt], quindi da manuale dovrebbe aspettare di essere incastrato in un contesto I/O per fare un qualcosa: e questo qualcosa è restituire una particolare lista. Ma da una chiamata alla successiva che succede, viene rifatto di volta in volta tutto il computo che definisce [tt]getCfgs[/tt]? Credo anche di non essere riuscito ad intendere perfettamente la differenza tra i due termini in corsivo in questo caso.

Iniziamo con un pezzo di un codice più vasto e scorporato per quello che mi serve sapere.
-- questo è il file Cfg.hs module Cfg where import Control.Monad (liftM) import Control.Conditional (ifM) import System.Directory (doesFileExist) import System.Environment.FindBin (getProgPath) type Maybe2 a = (Maybe a, Maybe a) getCfgs :: IO [Maybe2 String] getCfgs = do thisFile <- liftM (++ "/configs") getProgPath ifM (doesFileExist thisFile) (liftM (map chop . lines) (readFile thisFile)) (return []) where chop :: String -> Maybe2 String chop str = case words str of x:y:_ -> (Just x , Just y) x:[] -> (Just x , Nothing) _ -> (Nothing, Nothing) getCfgValOf :: String -> IO (Maybe String) getCfgValOf key = liftM h getCfgs where h :: [Maybe2 String] -> Maybe String h [] = Nothing h ((a, b):xs) | a == Just key = b | otherwise = h xs
In realtà in questo caso non è cruciale sapere cose fanno le funzioni, piuttosto sapere i tipi delle cose.
Prendiamo un [tt]main[/tt] così
import Cfg (getCfgValOf) main :: IO () main = mapM printCfgValOf [ "GRUB_DEFAULT" , "GRUB_TIMEOUT_STYLE" , "GRUB_TIMEOUT" , "GRUB_CMDLINE_LINUX_DEFAULT" ] where printCfgValOf :: String -> IO () printCfgValOf key = print =<< getCfgValOf key
Mi chiedo che fine faccia quel [tt]getCfgs :: IO (Maybe2 String)[/tt] richiamato da definizione dietro le quinte ben quattro volte consecutive. Mi sto riferendo a questo pezzo su Real World Haskell
What Is An I/O Action?
Actions:
[...]
* Produce an effect when performed, but not when evaluated. That is, they only produce an effect when called by something else in an I/O context.
[...]
Cosa fa il calcolatore? È un [tt]IO qualcosa[/tt], quindi da manuale dovrebbe aspettare di essere incastrato in un contesto I/O per fare un qualcosa: e questo qualcosa è restituire una particolare lista. Ma da una chiamata alla successiva che succede, viene rifatto di volta in volta tutto il computo che definisce [tt]getCfgs[/tt]? Credo anche di non essere riuscito ad intendere perfettamente la differenza tra i due termini in corsivo in questo caso.
Risposte
Può essere che la differenza tra i due termini in corsivo sia la differenza tra lazy evaluation e non-lazy?
Per prima cosa tutto viene valutato usando la lazy evaluation in Haskell a meno di fare uso di particolari costrutti che forzano la valutazione di una espressione. Non si tratta quindi di lazy evaluation vs strict evaluation.
Quello che si dice in questo caso è diverso ed è in qualche modo uguale per tutti i monadi. Per spiegarlo è in effetti forse più semplice considerare un altro tipo di monade: [tt]((->) e)[/tt] (le funzioni con primo argomento fissato). Possiamo pensare a questo monade come ad un insieme di "azioni" eseguite all'interno di un determinato ambiente di tipo [tt]e[/tt]. Una funzione in questo monade non produce alcun valore, ma solo una funzione che, quando il suo valore sarà infine richiesto, produrrà qualcosa. [tt]IO[/tt] è simile nel senso che produrrà un risultato solo quando richiamata direttamente o indirettamente (attraverso funzioni a loro volta nel monade [tt]IO[/tt]) dal main (oppure qualcosa che fa uso di [tt]unsafePerformIO[/tt]).
Quello che si dice in questo caso è diverso ed è in qualche modo uguale per tutti i monadi. Per spiegarlo è in effetti forse più semplice considerare un altro tipo di monade: [tt]((->) e)[/tt] (le funzioni con primo argomento fissato). Possiamo pensare a questo monade come ad un insieme di "azioni" eseguite all'interno di un determinato ambiente di tipo [tt]e[/tt]. Una funzione in questo monade non produce alcun valore, ma solo una funzione che, quando il suo valore sarà infine richiesto, produrrà qualcosa. [tt]IO[/tt] è simile nel senso che produrrà un risultato solo quando richiamata direttamente o indirettamente (attraverso funzioni a loro volta nel monade [tt]IO[/tt]) dal main (oppure qualcosa che fa uso di [tt]unsafePerformIO[/tt]).
Solo una piccola nota sul libro da te scelto. È stato pubblicato nel 2008 e Haskell è cambiato molto negli anni. In effetti l'ultima versione standard del linguaggio è del 2010 e una nuova versione è prevista a breve.. La principale modifica consiste in una esplosione di typeclasses inspirate alla teoria delle categorie che si possono per esempio trovare qui. Più di recente ci sono le librerie "ottiche" che sono per esempio spiegate qui.
Sì, è tutto lazy. Ho provato a spiegarmi la differenza tra quei due termini. Un caso molto più stupido è questo: [tt]2 + 3[/tt] in un linguaggio come Python, viene bellamente valutato senza pensarci un secondo in [tt]5[/tt]. Haskell invece di base pensa a non performare l'addizione, preferendosi portare a spasso [tt](+) 2 3[/tt] così com'è (destinandogli uno thunk di memoria). Bisogna costringerlo a eseguire il calcolo.
Principalmente faccio fatica a rappresentarmi le cose di tipo [tt]IO qualcosa[/tt], e questo è il caso.
L'evaluation di [tt]getCfgs[/tt] cos'è? Il sapere come è definito, ma non fare niente nel momento in cui, definito, viene valutato a qualcosa. Questo, sempre destinando, dello spazio di meoria per quello che si trascina dietro. Invece in contesto IO, viene eseguito, e sputa fuori un risultato (una IO lista).
Essendo l'altra funzione così come l'ho data, [tt]getCfgs[/tt] valutato non fa nulla, chiamato da [tt]getCfgValOf[/tt] all'interno di un [tt]main[/tt], è costretto a fare qualcosa. Ma tra una chiamata e la successiva che succede?
Viene forzato a rifare la stessa cosa ogni volta (buttare fuori una IO lista)? Oppure mantiene memoria di quello che ha fatto, e non ricalcola la stessa cosa successivamente?
Principalmente faccio fatica a rappresentarmi le cose di tipo [tt]IO qualcosa[/tt], e questo è il caso.
getCfgs :: IO [Maybe2 String] getCfgs = do thisFile <- liftM (++ "/configs") getProgPath ifM (doesFileExist thisFile) (liftM (map chop . lines) (readFile thisFile)) (return []) where chop :: String -> Maybe2 String chop str = case words str of x:y:_ -> (Just x , Just y) x:[] -> (Just x , Nothing) _ -> (Nothing, Nothing)
L'evaluation di [tt]getCfgs[/tt] cos'è? Il sapere come è definito, ma non fare niente nel momento in cui, definito, viene valutato a qualcosa. Questo, sempre destinando, dello spazio di meoria per quello che si trascina dietro. Invece in contesto IO, viene eseguito, e sputa fuori un risultato (una IO lista).
Essendo l'altra funzione così come l'ho data, [tt]getCfgs[/tt] valutato non fa nulla, chiamato da [tt]getCfgValOf[/tt] all'interno di un [tt]main[/tt], è costretto a fare qualcosa. Ma tra una chiamata e la successiva che succede?
import Cfg main :: IO () main = do print =<< getCfgValOf "GRUB_DEFAULT" print =<< getCfgValOf "GRUB_TIMEOUT_STYLE" print =<< getCfgValOf "GRUB_TIMEOUT" print =<< getCfgValOf "GRUB_CMDLINE_LINUX_DEFAULT"
Viene forzato a rifare la stessa cosa ogni volta (buttare fuori una IO lista)? Oppure mantiene memoria di quello che ha fatto, e non ricalcola la stessa cosa successivamente?
"apatriarca":
Solo una piccola nota sul libro da te scelto. È stato pubblicato nel 2008 e Haskell è cambiato molto negli anni.
Lo so bene. Il testo è stato scelto con lo scopo di incominciare a fare qualcosa di molto pratico da subito. Per quanto riguarda la teoria sottostante, studiando Teoria delle Categeorie ho occasione di sistemare le idee e di affinare i miei programmini. Quindi diciamo che di quel libro, tengo molte cose, ne rifiuto altre (l'espressione "teoria delle categoria" compare forse un paio di volte in tutto il libro, "funtore" non credo più volte).
Da un certo punto di vista IO è "magico" in haskell nel senso che la sua reale definizione vive al di fuori del linguaggio nel compilatore che trasforma quelle operazioni in qualcosa che viene eseguito ad un certo punto come in Python. Un monade fornisce una astrazione sulla vera natura dell'ambiente in cui esegui le tue azioni.
Credo che tutto venga eseguito più volte sinceramente, ma potrei in effetti sbagliarmi che non sono così esperto di queste cose. L'unico modo per essere certi è guardare l'output del compilatore.
Credo che tutto venga eseguito più volte sinceramente, ma potrei in effetti sbagliarmi che non sono così esperto di queste cose. L'unico modo per essere certi è guardare l'output del compilatore.
Una piccola nota sulla teoria delle categorie, ci sono diverse differenze tra i corrispondenti concetti in Haskell e matematica. Le definizioni sono infatti spesso diverse. Il Funtore in Haskell è per esempio un Endofuntore in matematica. La scelta delle funzioni fondamentali della definizione di un monade è diversa nelle due teorie..
"apatriarca":
Una piccola nota sulla teoria delle categorie, ci sono diverse differenze tra i corrispondenti concetti in Haskell e matematica. Le definizioni sono infatti spesso diverse. Il Funtore in Haskell è per esempio un Endofuntore in matematica. La scelta delle funzioni fondamentali della definizione di un monade è diversa nelle due teorie..
La definizione di monade è equivalente a quella matematica; per la prima osservazione, il motivo è che esiste un solo kind per i tipi: sai se questa cosa è stata/verrà cambiata in versioni più recenti, inserendo una maniera di "stratificare" gli universi, anche vedendo come viene gestita in Agda/Idris?
Non ho idea di cosa sia in programma per la nuova versione di Haskell..