Direi che la cosa migliore da fare non è come la chiameremmo, ma come dovremmo analizzare una tale parte di codice. E la mia prima domanda chiave in tale analisi sarebbe:
- L'effetto collaterale dipende dall'argomento della funzione o dal risultato dell'effetto collaterale?
-
No: la "funzione efficace" può essere rifatta in una funzione pura, un'azione efficace e un meccanismo per combinarli.
-
Sì: la "funzione efficace" è una funzione che produce un risultato monadico.
Questo è semplice da illustrare in Haskell (e questa frase è solo una mezza barzelletta). Un esempio di "no" potrebbe essere qualcosa del tipo:
double :: Num a => a -> IO a
double x = do
putStrLn "I'm doubling some number"
return (x*2)
In questo esempio l'azione che eseguiamo (stampa la riga "I'm doubling some number"
) non ha alcun impatto sulla relazione tra x
e il risultato. Ciò significa che possiamo refactoring in questo modo (utilizzando la classe Applicative
e il suo operatore *>
), che mostra che la funzione e l'effetto sono in realtà ortogonali:
double :: Num a => a -> IO a
double x = action *> pure (function x)
where
-- The pure function
function x = x*2
-- The side effect
action = putStrLn "I'm doubling some number"
Quindi in questo caso, personalmente, direi che è un caso in cui è possibile calcolare una funzione pura. Gran parte della programmazione di Haskell riguarda proprio questo: imparare a calcolare le parti pure dal codice efficace.
Un esempio di tipo "sì", in cui le parti pure e quelle effettive non sono ortogonali:
double :: Num a => a -> IO a
double x = do
putStrLn ("I'm doubling the number " ++ show x)
return (x*2)
Ora, la stringa da stampare dipende dal valore di x
. La parte funzione (moltiplica x
per due), tuttavia, non dipende affatto dall'effetto, quindi possiamo ancora considerarlo:
logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
logger a
return (function a)
double x = logged function logger
where function = (*2)
logger x putStrLn ("I'm doubling the number " ++ show x)
Potrei continuare a sillabare altri esempi, ma spero che questo sia sufficiente per illustrare il punto con cui ho iniziato: non lo si "chiama" qualcosa, si analizza come le parti pure e quelle effettive si relazionano e si valutano quando è a tuo vantaggio.
Questo è uno dei motivi per cui Haskell utilizza la sua classe Monad
in modo estensivo. Le Monade sono (tra le altre cose) uno strumento per eseguire questo tipo di analisi e refactoring.