Récemment, j'ai commencé à apprendre Haskell parce que je voulais élargir mes connaissances sur la programmation fonctionnelle et je dois dire que je l'aime vraiment jusqu'à présent. La ressource que j'utilise actuellement est le cours «Haskell Fundamentals Part 1» sur Pluralsight. Malheureusement, j'ai du mal à comprendre une citation particulière du conférencier au sujet du code suivant et j'espérais que vous pourriez éclairer le sujet.
Code d'accompagnement
helloWorld :: IO ()
helloWorld = putStrLn "Hello World"
main :: IO ()
main = do
helloWorld
helloWorld
helloWorld
La citation
Si vous avez la même action IO plusieurs fois dans un do-block, elle sera exécutée plusieurs fois. Ce programme imprime donc la chaîne «Hello World» trois fois. Cet exemple permet d'illustrer que ce putStrLn
n'est pas une fonction avec des effets secondaires. Nous appelons la putStrLn
fonction une fois pour définir la helloWorld
variable. Si cela putStrLn
avait pour effet secondaire d'imprimer la chaîne, elle ne s'imprimerait qu'une seule fois et la helloWorld
variable répétée dans le do-block principal n'aurait aucun effet.
Dans la plupart des autres langages de programmation, un programme comme celui-ci n'imprimait 'Hello World' qu'une seule fois, car l'impression se produisait lorsque la putStrLn
fonction était appelée. Cette distinction subtile ferait souvent trébucher les débutants, alors pensez-y un peu et assurez-vous de comprendre pourquoi ce programme imprime `` Hello World '' trois fois et pourquoi il ne l'imprimerait qu'une seule fois si la putStrLn
fonction faisait l'impression comme effet secondaire.
Ce que je ne comprends pas
Pour moi, il semble presque naturel que la chaîne «Hello World» soit imprimée trois fois. Je perçois la helloWorld
variable (ou la fonction?) Comme une sorte de rappel qui est invoqué plus tard. Ce que je ne comprends pas, c'est que si cela putStrLn
avait un effet secondaire, cela entraînerait l'impression de la chaîne une seule fois. Ou pourquoi il ne serait imprimé qu'une seule fois dans d'autres langages de programmation.
Disons que dans le code C #, je suppose que cela ressemblerait à ceci:
C # (violon)
using System;
public class Program
{
public static void HelloWorld()
{
Console.WriteLine("Hello World");
}
public static void Main()
{
HelloWorld();
HelloWorld();
HelloWorld();
}
}
Je suis sûr que j'oublie quelque chose d'assez simple ou que je interprète mal sa terminologie. Toute aide serait grandement appréciée.
ÉDITER:
Merci à tous pour vos réponses! Vos réponses m'ont aidé à mieux comprendre ces concepts. Je ne pense pas qu'il ait complètement cliqué pour le moment, mais je reviendrai sur le sujet à l'avenir, merci!
helloWorld
être constant comme un champ ou une variable en C #. Aucun paramètre n'est appliquéhelloWorld
.putStrLn
n'a pas d'effet secondaire; il renvoie simplement une action IO, la même action IO pour l'argument,"Hello World"
quel que soit le nombre de fois que vous appelezputStrLn
.helloworld
serait pas une action qui s'imprimeHello world
; ce serait la valeur renvoyée parputStrLn
après son impressionHello World
(à savoir,()
).helloWorld = Console.WriteLine("Hello World");
. Vous venez de contenir laConsole.WriteLine("Hello World");
dans laHelloWorld
fonction à exécuter chaque fois que vousHelloWorld
invoquez. Pensez maintenant à ce quihelloWorld = putStrLn "Hello World"
faithelloWorld
. Il est affecté à une monade IO qui contient()
. Une fois que vous le lierez, il>>=
effectuera seulement son activité (impression de quelque chose) et vous donnera()
sur le côté droit de l'opérateur de liaison.Réponses:
Il serait probablement plus facile de comprendre ce que l'auteur veut dire si nous définissons
helloWorld
comme variable locale:que vous pourriez comparer à ce pseudocode de type C #:
Ie en C #
WriteLine
est une procédure qui imprime son argument et ne renvoie rien. Dans Haskell,putStrLn
est une fonction qui prend une chaîne et vous donne une action qui afficherait cette chaîne si elle devait être exécutée. Cela signifie qu'il n'y a absolument aucune différence entre l'écritureet
Cela étant dit, dans cet exemple, la différence n'est pas particulièrement profonde, donc c'est bien si vous n'obtenez pas tout à fait ce que l'auteur essaie de comprendre dans cette section et que vous passez à autre chose pour l'instant.
cela fonctionne un peu mieux si vous le comparez à python
Le point ici étant que les actions d'E / S dans Haskell sont des valeurs «réelles» qui n'ont pas besoin d'être encapsulées dans d'autres «rappels» ou quoi que ce soit du genre pour les empêcher de s'exécuter - plutôt, la seule façon de les amener à s'exécuter est pour les mettre dans un endroit particulier (c'est-à-dire quelque part à l'intérieur
main
ou un fil engendrémain
).Ce n'est pas seulement une astuce de salon non plus, cela finit par avoir des effets intéressants sur la façon dont vous écrivez du code (par exemple, cela fait partie de la raison pour laquelle Haskell n'a pas vraiment besoin des structures de contrôle communes que vous connaissez avec des langages impératifs et peut à la place tout faire en termes de fonctions), mais encore une fois, je ne m'inquiéterais pas trop à ce sujet (des analogies comme celles-ci ne cliquent pas toujours immédiatement)
la source
Il pourrait être plus facile de voir la différence comme décrit si vous utilisez une fonction qui fait réellement quelque chose, plutôt que
helloWorld
. Pensez à ce qui suit:Cela imprimera "j'ajoute 2 et 3" 3 fois.
En C #, vous pouvez écrire ce qui suit:
Qui n'imprimerait qu'une seule fois.
la source
Si l'évaluation a
putStrLn "Hello World"
eu des effets secondaires, alors le message ne sera imprimé qu'une seule fois.Nous pouvons approximer ce scénario avec le code suivant:
unsafePerformIO
prend uneIO
action et «oublie» que c'est uneIO
action, la détache de l'enchaînement habituel imposé par la composition desIO
actions et laisse l'effet se produire (ou non) selon les caprices de l'évaluation paresseuse.evaluate
prend une valeur pure et garantit que la valeur est évaluée chaque fois que l'IO
action résultante est évaluée - ce qui pour nous, ce sera, car elle se situe dans le chemin demain
. Nous l'utilisons ici pour relier l'évaluation de certaines valeurs à l'exection du programme.Ce code imprime une seule fois "Hello World". Nous traitons
helloWorld
comme une valeur pure. Mais cela signifie qu'il sera partagé entre tous lesevaluate helloWorld
appels. Et pourquoi pas? C'est une pure valeur après tout, pourquoi la recalculer inutilement? La premièreevaluate
action "fait apparaître" l'effet "caché" et les actions ultérieures évaluent simplement le résultat()
, ce qui ne provoque aucun autre effet.la source
unsafePerformIO
à ce stade de l'apprentissage de Haskell. Il a «dangereux» dans le nom pour une raison, et vous ne devriez pas l'utiliser à moins que vous puissiez (et avez) soigneusement considérer les implications de son utilisation dans le contexte. Le code que danidiaz a mis dans la réponse capture parfaitement le type de comportement non intuitif qui peut en résulterunsafePerformIO
.Il y a un détail à noter: vous n'appelez
putStrLn
fonction qu'une seule fois, tout en définissanthelloWorld
. Enmain
fonction, vous utilisez simplement la valeur de retour de celaputStrLn "Hello, World"
trois fois.Le conférencier dit que l'
putStrLn
appel n'a pas d'effets secondaires et c'est vrai. Mais regardez le type dehelloWorld
- c'est une action d'E / S.putStrLn
le crée juste pour vous. Plus tard, vous enchaînez 3 d'entre eux avec ledo
bloc pour créer une autre action IO -main
. Plus tard, lorsque vous exécuterez votre programme, cette action sera exécutée, c'est là que se situent les effets secondaires.Le mécanisme qui se trouve à la base de cela - les monades . Ce concept puissant vous permet d'utiliser certains effets secondaires comme l'impression dans un langage qui ne prend pas directement en charge les effets secondaires. Vous enchaînez juste quelques actions, et cette chaîne sera exécutée au démarrage de votre programme. Vous devrez comprendre ce concept en profondeur si vous souhaitez utiliser Haskell sérieusement.
la source