Comment une fonction temporelle peut-elle exister dans la programmation fonctionnelle?

646

Je dois admettre que je ne connais pas grand-chose à la programmation fonctionnelle. Je l'ai lu ici et là, et j'ai donc appris que dans la programmation fonctionnelle, une fonction renvoie la même sortie, pour la même entrée, peu importe combien de fois la fonction est appelée. C'est exactement comme une fonction mathématique qui évalue à la même sortie pour la même valeur des paramètres d'entrée qui implique dans l'expression de fonction.

Par exemple, considérez ceci:

f(x,y) = x*x + y; // It is a mathematical function

Peu importe combien de fois vous utilisez f(10,4), sa valeur sera toujours 104. En tant que tel, où que vous ayez écrit f(10,4), vous pouvez le remplacer par 104, sans modifier la valeur de l'expression entière. Cette propriété est appelée transparence référentielle d'une expression.

Comme le dit Wikipedia ( lien ),

Inversement, dans le code fonctionnel, la valeur de sortie d'une fonction dépend uniquement des arguments qui sont entrés dans la fonction, donc appeler une fonction f deux fois avec la même valeur pour un argument x produira le même résultat f (x) les deux fois.

Une fonction temporelle (qui renvoie l' heure actuelle ) peut-elle exister dans la programmation fonctionnelle?

  • Si oui, comment peut-il exister? Ne viole-t-il pas le principe de programmation fonctionnelle? Il viole particulièrement la transparence référentielle qui est une des propriétés de la programmation fonctionnelle (si je comprends bien).

  • Sinon, comment savoir l'heure actuelle de la programmation fonctionnelle?

Nawaz
la source
15
Je pense que la plupart (ou tous) les langages fonctionnels ne sont pas aussi stricts et combinent une programmation fonctionnelle et impérative. C'est du moins mon impression de F #.
Alex F
13
@Adam: Comment l'appelant pourrait-il savoir l'heure actuelle en premier lieu?
Nawaz
29
@Adam: En fait, c'est illégal (comme dans: impossible) dans les langages purement fonctionnels.
sepp2k
47
@Adam: À peu près. Un langage à usage général qui est pur offre généralement une certaine facilité pour atteindre "l'état du monde" (c'est-à-dire des choses comme l'heure actuelle, des fichiers dans un répertoire, etc.) sans briser la transparence référentielle. Dans Haskell, c'est la monade IO et dans Clean, c'est le type mondial. Donc, dans ces langages, une fonction qui a besoin de l'heure actuelle la prendrait comme argument ou elle devrait retourner une action d'E / S au lieu de son résultat réel (Haskell) ou prendre l'état du monde comme argument (Clean).
sepp2k du
12
En pensant à la PF, il est facile d'oublier: un ordinateur est un gros morceau d'état mutable. FP ne change rien à cela, il le cache simplement.
Daniel

Réponses:

176

Une autre façon de l'expliquer est la suivante: aucune fonction ne peut obtenir l'heure actuelle (car elle ne cesse de changer), mais une action peut obtenir l'heure actuelle. Disons que getClockTimec'est une constante (ou une fonction nulle, si vous voulez) qui représente l' action d'obtenir l'heure actuelle. Cette action est la même à chaque fois, peu importe quand elle est utilisée, c'est donc une vraie constante.

De même, disons que printc'est une fonction qui prend un peu de temps et l'imprime sur la console. Étant donné que les appels de fonction ne peuvent pas avoir d'effets secondaires dans un langage fonctionnel pur, nous imaginons plutôt que c'est une fonction qui prend un horodatage et renvoie l' action de l'imprimer sur la console. Encore une fois, c'est une fonction réelle, car si vous lui donnez le même horodatage, il retournera la même action de l'imprimer à chaque fois.

Maintenant, comment pouvez-vous imprimer l'heure actuelle sur la console? Eh bien, vous devez combiner les deux actions. Alors, comment pouvons-nous faire cela? Nous ne pouvons pas simplement passer getClockTimeà print, puisque print attend un horodatage, pas une action. Mais nous pouvons imaginer qu'il existe un opérateur, >>=qui combine deux actions, une qui obtient un horodatage et une qui en prend une comme argument et l'imprime. En appliquant cela aux actions mentionnées précédemment, le résultat est ... tadaaa ... une nouvelle action qui obtient l'heure actuelle et l'imprime. Et c'est d'ailleurs exactement comment cela se fait à Haskell.

Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

Donc, conceptuellement, vous pouvez le visualiser de cette façon: Un programme fonctionnel pur n'effectue aucune E / S, il définit une action , que le système d'exécution exécute ensuite. L' action est la même à chaque fois, mais le résultat de son exécution dépend des circonstances de son exécution.

Je ne sais pas si c'était plus clair que les autres explications, mais cela m'aide parfois à y penser de cette façon.

dainichi
la source
33
Ce n'est pas convaincant pour moi. Vous avez commodément appelé getClockTimeune action au lieu d'une fonction. Eh bien, si vous appelez ainsi, puis appelez chaque action de fonction , alors même la programmation impérative deviendrait une programmation fonctionnelle. Ou peut - être, vous voulez l' appeler actionnelle programmming.
Nawaz
92
@Nawaz: L'élément clé à noter ici est que vous ne pouvez pas exécuter une action à partir d'une fonction. Vous ne pouvez combiner que des actions et des fonctions pour créer de nouvelles actions. La seule façon d'exécuter une action est de la composer dans votre mainaction. Cela permet de séparer le code fonctionnel pur du code impératif, et cette séparation est imposée par le système de type. Traiter les actions comme des objets de première classe vous permet également de les faire circuler et de créer vos propres "structures de contrôle".
hammar
36
Tout dans Haskell n'est pas une fonction - c'est un non-sens absolu. Une fonction est quelque chose dont le type contient un ->- c'est ainsi que la norme définit le terme et c'est vraiment la seule définition sensée dans le contexte de Haskell. Donc, quelque chose dont le type IO Whatevern'est pas une fonction.
sepp2k
9
@ sepp2k Donc, myList :: [a -> b] est une fonction? ;)
fuz
8
@ThomasEding Je suis vraiment en retard à la fête, mais je veux juste clarifier ceci: ce putStrLnn'est pas une action - c'est une fonction qui renvoie une action. getLineest une variable qui contient une action. Les actions sont les valeurs, les variables et les fonctions sont les "conteneurs" / "étiquettes" que nous donnons à ces actions.
kqr
356

Oui et non.

Différents langages de programmation fonctionnels les résolvent différemment.

Dans Haskell (très pur), tout cela doit arriver dans quelque chose appelé la Monade d'E / S - voir ici .

Vous pouvez penser à cela comme obtenir une autre entrée (et sortie) dans votre fonction (l'état du monde) ou plus facilement comme un endroit où "l'impureté" comme obtenir le changement d'heure se produit.

D'autres langages comme F # ont juste une impureté intégrée et vous pouvez donc avoir une fonction qui renvoie des valeurs différentes pour la même entrée - tout comme les langages impératifs normaux .

Comme Jeffrey Burka l'a mentionné dans son commentaire: Voici la belle introduction à la Monade d'E / S directement depuis le wiki Haskell.

Carsten
la source
223
La chose cruciale à réaliser à propos de la monade IO à Haskell est que ce n'est pas seulement un hack pour contourner ce problème; les monades sont une solution générale au problème de la définition d'une séquence d'actions dans un certain contexte. Un contexte possible est le monde réel, pour lequel nous avons la monade IO. Un autre contexte se trouve dans une transaction atomique, pour laquelle nous avons la monade STM. Un autre contexte encore réside dans l'implémentation d'un algorithme procédural (par exemple le shuffle de Knuth) en tant que fonction pure, pour laquelle nous avons la monade ST. Et vous pouvez également définir vos propres monades. Les monades sont une sorte de point-virgule surchargeable.
Paul Johnson
2
Je trouve utile de ne pas appeler des choses comme obtenir les "fonctions" de l'heure actuelle mais quelque chose comme des "procédures" (bien que l'on puisse soutenir que la solution Haskell fait exception à cela).
singpolyma
du point de vue de Haskell, les "procédures" classiques (des choses qui ont des types comme '... -> ()') sont quelque peu triviales car une fonction pure avec ... -> () ne peut rien faire du tout.
Carsten
3
Le terme typique de Haskell est "action".
Sebastian Redl
6
"Les monades sont une sorte de point-virgule surchargeable." +1
user2805751
147

Dans Haskell, on utilise une construction appelée monade pour gérer les effets secondaires. Une monade signifie essentiellement que vous encapsulez des valeurs dans un conteneur et que vous avez certaines fonctions pour chaîner des fonctions des valeurs aux valeurs dans un conteneur. Si notre conteneur a le type:

data IO a = IO (RealWorld -> (a,RealWorld))

nous pouvons implémenter en toute sécurité les actions d'E / S. Ce type signifie: Une action de type IOest une fonction, qui prend un jeton de type RealWorldet renvoie un nouveau jeton, accompagné d'un résultat.

L'idée derrière cela est que chaque action IO mute l'état extérieur, représenté par le jeton magique RealWorld. À l'aide de monades, on peut enchaîner plusieurs fonctions qui mutent le monde réel ensemble. La fonction la plus importante d'une monade est la liaison>>= prononcée :

(>>=) :: IO a -> (a -> IO b) -> IO b

>>=prend une action et une fonction qui prend le résultat de cette action et en crée une nouvelle. Le type de retour est la nouvelle action. Par exemple, supposons qu'il existe une fonction now :: IO String, qui renvoie une chaîne représentant l'heure actuelle. Nous pouvons l'enchaîner avec la fonction putStrLnpour l'imprimer:

now >>= putStrLn

Ou écrit en do-Notation, qui est plus familier à un programmeur impératif:

do currTime <- now
   putStrLn currTime

Tout cela est pur, car nous cartographions la mutation et les informations sur le monde extérieur au RealWorldjeton. Donc à chaque fois que vous exécutez cette action, vous obtenez bien sûr une sortie différente, mais l'entrée n'est pas la même: le RealWorldjeton est différent.

fuz
la source
3
-1: Je ne suis pas satisfait de l' RealWorldécran de fumée. Pourtant, la chose la plus importante est de savoir comment ce prétendu objet est transmis dans une chaîne. La pièce manquante est là où elle commence, où se trouve la source ou la connexion au monde réel - elle commence par la fonction principale qui s'exécute dans la monade IO.
u0b34a0f6ae
2
@ kaizer.se Vous pouvez penser à un RealWorldobjet global qui est passé dans le programme au démarrage.
fuz
6
Fondamentalement, votre mainfonction prend un RealWorldargument. Ce n'est que lors de l'exécution qu'il est passé.
Louis Wasserman
13
Vous voyez, la raison pour laquelle ils cachent le RealWorldet ne fournissent que des fonctions chétives pour le changer putStrLn, c'est que certains programmeurs Haskell ne changent pas RealWorldavec l'un de leurs programmes de telle sorte que l'adresse et la date de naissance de Haskell Curry sont telles qu'ils deviennent des voisins d'à côté grandir (cela pourrait endommager le continuum espace-temps de manière à nuire au langage de programmation Haskell.)
PyRulez
2
RealWorld -> (a, RealWorld) ne se décompose pas en tant que métaphore même sous concurrence, tant que vous gardez à l'esprit que le monde réel peut être modifié par d'autres parties de l'univers en dehors de votre fonction (ou de votre processus actuel) à tout moment. Donc (a) le méthaphre ne se décompose pas, et (b) chaque fois qu'une valeur qui a RealWorldcomme type est passée à une fonction, la fonction doit être réévaluée, car le monde réel aura changé entre-temps ( qui est modélisé comme @fuz l'a expliqué, en redonnant une `` valeur symbolique '' différente à chaque fois que nous interagissons avec le monde réel).
Qqwy
73

La plupart des langages de programmation fonctionnels ne sont pas purs, c'est-à-dire qu'ils permettent aux fonctions non seulement de dépendre de leurs valeurs. Dans ces langues, il est parfaitement possible d'avoir une fonction renvoyant l'heure actuelle. Parmi les langues que vous avez marquées, cette question s'applique à Scala et F # (ainsi qu'à la plupart des autres variantes de ML ).

Dans des langues comme Haskell et Clean , qui sont pures, la situation est différente. À Haskell, l'heure actuelle ne serait pas disponible via une fonction, mais une action dite IO, qui est la façon dont Haskell encapsule les effets secondaires.

Dans Clean, ce serait une fonction, mais la fonction prendrait une valeur mondiale comme argument et renverrait une nouvelle valeur mondiale (en plus de l'heure actuelle) comme résultat. Le système de type garantirait que chaque valeur mondiale ne peut être utilisée qu'une seule fois (et chaque fonction qui consomme une valeur mondiale en produirait une nouvelle). De cette façon, la fonction de temps devrait être appelée avec un argument différent à chaque fois et donc être autorisée à renvoyer une heure différente à chaque fois.

sepp2k
la source
2
Cela donne l'impression que Haskell et Clean font des choses différentes. D'après ce que je comprends, ils font de même, juste que Haskell offre une syntaxe plus agréable (?) Pour accomplir cela.
Konrad Rudolph
27
@Konrad: Ils font la même chose dans le sens où les deux utilisent les fonctionnalités du système de type pour abstraire les effets secondaires, mais c'est tout. Notez qu'il est très bien d'expliquer la monade IO en termes de type mondial, mais la norme Haskell ne définit pas réellement un type mondial et il n'est pas possible d'obtenir une valeur de type World dans Haskell (alors que c'est très possible et en effet nécessaire en propre). De plus, Haskell n'a pas le typage unique en tant que fonctionnalité de système de type, donc s'il vous donnait accès à un monde, il ne pourrait pas garantir que vous l'utilisiez de manière pure comme le fait Clean.
sepp2k
51

"L'heure actuelle" n'est pas une fonction. C'est un paramètre. Si votre code dépend de l'heure actuelle, cela signifie que votre code est paramétré par le temps.

Vlad Patryshev
la source
22

Cela peut absolument être fait de manière purement fonctionnelle. Il existe plusieurs façons de le faire, mais la plus simple consiste à faire en sorte que la fonction de temps renvoie non seulement l'heure mais également la fonction que vous devez appeler pour obtenir la prochaine mesure de temps .

En C #, vous pouvez l'implémenter comme ceci:

// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
    public static readonly ClockStamp ProgramStartTime = new ClockStamp();
    public readonly DateTime Time;
    private ClockStamp _next;

    private ClockStamp() {
        this.Time = DateTime.Now;
    }
    public ClockStamp NextMeasurement() {
        if (this._next == null) this._next = new ClockStamp();
        return this._next;
    }
}

(Gardez à l'esprit qu'il s'agit d'un exemple censé être simple et non pratique. En particulier, les nœuds de liste ne peuvent pas être récupérés car ils sont enracinés par ProgramStartTime.)

Cette classe 'ClockStamp' agit comme une liste chaînée immuable, mais en réalité, les nœuds sont générés à la demande afin qu'ils puissent contenir l'heure 'actuelle'. Toute fonction qui souhaite mesurer le temps doit avoir un paramètre 'clockStamp' et doit également retourner sa dernière mesure de temps dans son résultat (de sorte que l'appelant ne voit pas les anciennes mesures), comme ceci:

// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
    public readonly ClockStamp Time;
    public readonly T Value;
    public TimeStampedValue(ClockStamp time, T value) {
        this.Time = time;
        this.Value = value;
    }
}

// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
    var start = lastMeasurement.NextMeasurement();
    for (var i = 0; i < 10000000; i++) {
    }
    var end = start.NextMeasurement();
    var duration = end.Time - start.Time;
    return new TimeStampedValue<TimeSpan>(end, duration);
}

public static void Main(String[] args) {
    var clock = ClockStamp.ProgramStartTime;
    var r = TimeALoop(clock);
    var duration = r.Value; //the result
    clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}

Bien sûr, il est un peu gênant d'avoir à passer cette dernière mesure en entrée et en sortie, en entrée et en sortie, en entrée et en sortie. Il existe de nombreuses façons de masquer le passe-partout, en particulier au niveau de la conception de la langue. Je pense que Haskell utilise ce genre de truc et cache ensuite les parties laides en utilisant des monades.

Craig Gidney
la source
Intéressant, mais que i++dans la boucle for n'est pas référentiellement transparent;)
snim2
@ snim2 Je ne suis pas parfait. : P Consolez-vous du fait que la mutabilité sale n'affecte pas la transparence référentielle du résultat. Si vous passez deux fois la même «dernière mesure», vous obtenez une prochaine mesure périmée et retournez le même résultat.
Craig Gidney
@Strilanc Merci pour cela. Je pense que dans le code impératif, il est donc intéressant de voir les concepts fonctionnels expliqués de cette façon. Je peux alors imaginer une langue où ce naturel et syntaxiquement plus propre.
WW.
En fait, vous pouvez également suivre la voie de la monade en C #, évitant ainsi le passage explicite des horodatages. Vous avez besoin de quelque chose comme struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }. Mais le code avec cela ne serait toujours pas aussi joli que Haskell avec la dosyntaxe.
leftaroundabout
@leftaroundabout vous pouvez en quelque sorte faire semblant d'avoir une monade en C # en implémentant la fonction de liaison comme une méthode appelée SelectMany, qui active la syntaxe de compréhension des requêtes. Cependant, vous ne pouvez toujours pas programmer de manière polymorphe sur des monades, c'est donc une bataille difficile contre le système de type faible :(
sara
16

Je suis surpris qu'aucune des réponses ou des commentaires ne mentionne de charbon ou de coinduction. Habituellement, la coinduction est mentionnée lors du raisonnement sur les structures de données infinies, mais elle est également applicable à un flux infini d'observations, comme un registre de temps sur un CPU. Une houillère modélise un état caché; et des modèles de coinduction observant cet état. (Modèles d'induction normaux construisant l' état.)

Il s'agit d'un sujet brûlant dans la programmation fonctionnelle réactive. Si vous êtes intéressé par ce genre de choses, lisez ceci: http://digitalcommons.ohsu.edu/csetech/91/ (28 pp.)

Jeffrey Aguilera
la source
3
Et comment est-ce lié à cette question?
Nawaz
5
Votre question portait sur la modélisation du comportement dépendant du temps d'une manière purement fonctionnelle, par exemple, une fonction qui renvoie l'horloge système actuelle. Vous pouvez soit enfiler quelque chose d'équivalent à une monade d'E / S à travers toutes les fonctions et leur arbre de dépendance pour accéder à cet état; ou vous pouvez modéliser l'état en définissant les règles d'observation plutôt que les règles constructives. C'est pourquoi la modélisation inductive d'un état complexe dans la programmation fonctionnelle semble si peu naturelle, car l'état caché est vraiment une propriété coinductive .
Jeffrey Aguilera
Grande source! Y a-t-il quelque chose de plus récent? La communauté JS semble toujours aux prises avec des abstractions de données de flux.
Dmitri Zaitsev
12

Oui, il est possible pour une fonction pure de renvoyer l'heure, si elle lui est donnée comme paramètre. Argument de temps différent, résultat de temps différent. Formez ensuite d'autres fonctions du temps et combinez-les avec un vocabulaire simple de fonctions (-of-time) -transformantes (d'ordre supérieur). Étant donné que l'approche est sans état, le temps ici peut être continu (indépendant de la résolution) plutôt que discret, ce qui augmente considérablement la modularité . Cette intuition est à la base de la programmation réactive fonctionnelle (FRP).

Conal
la source
11

Oui! Vous avez raison! Now () ou CurrentTime () ou toute signature de méthode d'une telle saveur ne présente pas la transparence référentielle d'une manière. Mais par instruction au compilateur, il est paramétré par une entrée d'horloge système.

En sortie, Now () peut sembler ne pas suivre la transparence référentielle. Mais le comportement réel de l'horloge système et de la fonction au-dessus est conforme à la transparence référentielle.

MduSenthil
la source
11

Oui, une fonction d'obtention du temps peut exister dans la programmation fonctionnelle en utilisant une version légèrement modifiée de la programmation fonctionnelle connue sous le nom de programmation fonctionnelle impure (la valeur par défaut ou la principale est la programmation fonctionnelle pure).

En cas de temps (ou de lecture de fichier ou de lancement de missile), le code doit interagir avec le monde extérieur pour faire le travail et ce monde extérieur n'est pas basé sur les fondements purs de la programmation fonctionnelle. Pour permettre à un monde de programmation fonctionnelle pur d'interagir avec ce monde extérieur impur, les gens ont introduit une programmation fonctionnelle impure. Après tout, les logiciels qui n'interagissent pas avec le monde extérieur ne sont d'aucune utilité à part faire des calculs mathématiques.

Peu de langages de programmation de programmation fonctionnelle ont cette fonction d'impureté intégrée, de sorte qu'il n'est pas facile de séparer le code impur et le code pur (comme F #, etc.) et certains langages de programmation fonctionnels s'assurent que lorsque vous faites des choses impures ce code se démarque clairement du code pur, comme Haskell.

Une autre façon intéressante de voir cela serait que votre fonction get time dans la programmation fonctionnelle prendrait un objet "monde" qui a l'état actuel du monde comme le temps, le nombre de personnes vivant dans le monde, etc. l'objet serait toujours pur c'est-à-dire que vous passez dans le même état du monde que vous aurez toujours le même temps.

Ankur
la source
1
"Après tout, un logiciel qui n'interagit pas avec le monde extérieur n'est pas utile autrement que de faire des calculs mathématiques." Pour autant que je sache, même dans ce cas, l'entrée des calculs serait codée en dur dans le programme, également pas très utile. Dès que vous voulez lire les données d'entrée dans votre calcul mathématique à partir d'un fichier ou d'un terminal, vous avez besoin d'un code impur.
Giorgio
1
@Ankur: C'est exactement la même chose. Si le programme interagit avec autre chose que lui-même (par exemple le monde à travers son clavier, pour ainsi dire), il est toujours impur.
identité
1
@Ankur: Oui, je pense que vous avez raison! Même s'il peut ne pas être très pratique de transmettre des données d'entrée volumineuses sur la ligne de commande, cela peut être une manière pure de le faire.
Giorgio
2
Le fait d'avoir «l'objet du monde», y compris le nombre de personnes vivant dans le monde, élève l'ordinateur d'exécution à un niveau presque omniscient. Je pense que le cas normal est qu'il inclut des choses comme le nombre de fichiers sur votre disque dur et quel est le répertoire personnel de l'utilisateur actuel.
ziggystar
4
@ziggystar - "l'objet du monde" n'inclut en fait rien - c'est simplement un proxy pour l'état changeant du monde en dehors du programme. Son seul but est de marquer explicitement l'état mutable de manière à ce que le système de type puisse l'identifier.
Kris Nuttycombe
7

Votre question confond deux mesures liées d'un langage informatique: fonctionnelle / impérative et pure / impure.

Un langage fonctionnel définit les relations entre les entrées et les sorties des fonctions, et un langage impératif décrit les opérations spécifiques dans un ordre spécifique à effectuer.

Un langage pur ne crée ni ne dépend d'effets secondaires, et un langage impur les utilise partout.

Les programmes purs à cent pour cent sont pratiquement inutiles. Ils peuvent effectuer un calcul intéressant, mais comme ils ne peuvent pas avoir d'effets secondaires, ils n'ont ni entrée ni sortie, vous ne savez donc jamais ce qu'ils ont calculé.

Pour être utile, un programme doit être au moins un peu impur. Une façon de rendre un programme pur utile est de le placer dans un emballage mince et impur. Comme ce programme Haskell non testé:

-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
    putStrLn "Please enter the input parameter"
    inputStr <- readLine
    putStrLn "Starting time:"
    getCurrentTime >>= print
    let inputInt = read inputStr    -- this line is pure
    let result = fib inputInt       -- this is also pure
    putStrLn "Result:"
    print result
    putStrLn "Ending time:"
    getCurrentTime >>= print
NovaDenizen
la source
4
Il serait utile que vous puissiez aborder le problème spécifique de l'obtention du temps et expliquer un peu dans quelle mesure nous considérons les IOvaleurs et les résultats purs.
AndrewC
En fait, même des programmes 100% purs chauffent le CPU, ce qui est un effet secondaire.
Jörg W Mittag
3

Vous abordez un sujet très important dans la programmation fonctionnelle, c'est-à-dire effectuer des E / S. La façon dont de nombreux langages purs s'y prennent consiste à utiliser des langages intégrés spécifiques au domaine, par exemple un sous-langage dont la tâche consiste à coder des actions , qui peuvent avoir des résultats.

Le runtime Haskell par exemple s'attend à ce que je définisse une action appelée mainqui est composée de toutes les actions qui composent mon programme. Le runtime exécute ensuite cette action. La plupart du temps, ce faisant, il exécute du code pur. De temps en temps, le runtime utilise les données calculées pour effectuer des E / S et renvoie les données en code pur.

Vous pourriez vous plaindre que cela ressemble à de la triche, et d'une certaine manière: en définissant des actions et en attendant que le runtime les exécute, le programmeur peut faire tout ce qu'un programme normal peut faire. Mais le système de type fort de Haskell crée une forte barrière entre les parties pures et "impures" du programme: vous ne pouvez pas simplement ajouter, disons, deux secondes au temps CPU actuel, et l'imprimer, vous devez définir une action qui se traduit par le courant Temps CPU, et passez le résultat à une autre action qui ajoute deux secondes et imprime le résultat. Écrire trop d'un programme est cependant considéré comme un mauvais style, car il est difficile de déduire quels effets sont causés, par rapport aux types Haskell qui nous disent tout ce que nous pouvons savoir sur ce qu'est une valeur.

Exemple: clock_t c = time(NULL); printf("%d\n", c + 2);en C, vs main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)à Haskell. L'opérateur >>=est utilisé pour composer des actions, en passant le résultat de la première à une fonction entraînant la deuxième action. Cela semble assez mystérieux, les compilateurs Haskell prennent en charge le sucre syntaxique qui nous permet d'écrire le dernier code comme suit:

type Clock = Integer -- To make it more similar to the C code

-- An action that returns nothing, but might do something
main :: IO ()
main = do
    -- An action that returns an Integer, which we view as CPU Clock values
    c <- getCPUTime :: IO Clock
    -- An action that prints data, but returns nothing
    print (c + 2*1000*1000*1000*1000) :: IO ()

Ce dernier semble assez impératif, n'est-ce pas?

MauganRa
la source
1

Si oui, comment peut-il exister? Ne viole-t-il pas le principe de programmation fonctionnelle? Il viole particulièrement la transparence référentielle

Il n'existe pas dans un sens purement fonctionnel.

Sinon, comment savoir l'heure actuelle de la programmation fonctionnelle?

Il peut d'abord être utile de savoir comment une heure est récupérée sur un ordinateur. Il existe essentiellement des circuits embarqués qui gardent une trace de l'heure (c'est la raison pour laquelle un ordinateur aurait généralement besoin d'une petite batterie cellulaire). Ensuite, il peut y avoir un processus interne qui définit la valeur du temps à un certain registre de mémoire. Ce qui se résume essentiellement à une valeur qui peut être récupérée par le CPU.


Pour Haskell, il existe un concept d '«action d'E / S» qui représente un type qui peut être créé pour exécuter un processus d'E / S. Ainsi, au lieu de référencer une timevaleur, nous référençons une IO Timevaleur. Tout cela serait purement fonctionnel. Nous ne référençons pas timemais quelque chose dans le sens de «lire la valeur du registre de temps» .

Lorsque nous exécutons réellement le programme Haskell, l'action d'E / S a effectivement lieu.

Chris Stryczynski
la source