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?
Réponses:
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
getClockTime
c'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
print
c'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.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.
la source
getClockTime
une 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.main
action. 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".->
- 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 typeIO Whatever
n'est pas une fonction.putStrLn
n'est pas une action - c'est une fonction qui renvoie une action.getLine
est 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.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.
la source
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:
nous pouvons implémenter en toute sécurité les actions d'E / S. Ce type signifie: Une action de type
IO
est une fonction, qui prend un jeton de typeRealWorld
et 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 :>>=
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 fonctionnow :: IO String
, qui renvoie une chaîne représentant l'heure actuelle. Nous pouvons l'enchaîner avec la fonctionputStrLn
pour l'imprimer:Ou écrit en
do
-Notation, qui est plus familier à un programmeur impératif:Tout cela est pur, car nous cartographions la mutation et les informations sur le monde extérieur au
RealWorld
jeton. 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: leRealWorld
jeton est différent.la source
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.RealWorld
objet global qui est passé dans le programme au démarrage.main
fonction prend unRealWorld
argument. Ce n'est que lors de l'exécution qu'il est passé.RealWorld
et ne fournissent que des fonctions chétives pour le changerputStrLn
, c'est que certains programmeurs Haskell ne changent pasRealWorld
avec 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.)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 aRealWorld
comme 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).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.
la source
"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.
la source
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:
(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:
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.
la source
i++
dans la boucle for n'est pas référentiellement transparent;)struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }
. Mais le code avec cela ne serait toujours pas aussi joli que Haskell avec lado
syntaxe.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 :(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.)
la source
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).
la source
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.
la source
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.
la source
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é:
la source
IO
valeurs et les résultats purs.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
main
qui 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, vsmain = 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:Ce dernier semble assez impératif, n'est-ce pas?
la source
Il n'existe pas dans un sens purement fonctionnel.
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
time
valeur, nous référençons uneIO Time
valeur. Tout cela serait purement fonctionnel. Nous ne référençons pastime
mais 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.
la source