web-dev-qa-db-fra.com

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

Je dois admettre que je ne connais pas beaucoup la programmation fonctionnelle. J'ai lu quelque chose à ce sujet 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, quel que soit le nombre d'appels de la fonction. 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 l'expression de la fonction.

Par exemple, considérons ceci:

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

Peu importe le nombre de fois que vous utilisez f(10,4), sa valeur sera toujours 104. Ainsi, partout où vous avez é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 ),

À l'inverse, dans le code fonctionnel, la valeur de sortie d'une fonction dépend uniquement des arguments entrés dans la fonction. Par conséquent, l'appel d'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 de temps (qui renvoie le temps actuel ) existe-t-elle dans la programmation fonctionnelle?

  • Si oui, alors comment peut-il exister? Ne viole-t-il pas le principe de la programmation fonctionnelle? Cela viole en particulier transparence référentielle , propriété de la programmation fonctionnelle (si je le comprends bien).

  • Ou si non, alors comment peut-on connaître l'heure actuelle en programmation fonctionnelle?

625
Nawaz

Une autre façon de l'expliquer est la suivante: no fonction peut obtenir l'heure actuelle (car il change constamment), mais un action peut obtenir l'heure actuelle. Disons que getClockTime est une constante (ou une fonction nullary, si vous voulez) qui représente l'action action pour obtenir l'heure actuelle. Cette action est la même à chaque fois, peu importe la date d'utilisation, il s'agit donc d'une constante réelle.

De même, disons que print est une fonction qui prend une certaine représentation temporelle et l’imprime sur la console. Etant donné que les appels de fonction ne peuvent pas avoir d’effets secondaires dans un langage purement fonctionnel, nous imaginons au contraire que c’est une fonction qui prend un horodatage et renvoie le 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 le 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, car print attend un horodatage et non une action. Mais on peut 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 comme cela se passe à Haskell.

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

Donc, conceptuellement, vous pouvez le voir 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 action est toujours la même, mais le résultat de son exécution dépend des circonstances de son exécution.

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

160
dainichi

Oui et non.

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

En Haskell (très pur), tout cela doit arriver dans quelque chose appelé Monad I/O - voir ici .

Vous pouvez penser à cela comme obtenir une autre entrée (et une autre sortie) dans votre fonction (l'état-monde) ou plus facile comme un endroit où "l'impureté", comme obtenir l'heure changeante, se produit.

D'autres langues telles que F # ont simplement une impureté intégrée et vous pouvez donc avoir une fonction qui renvoie différentes valeurs pour la même entrée - tout comme les langues normales impératives.

Comme Jeffrey Burka l'a mentionné dans son commentaire: Voici l'introduction de Nice dans la Monade d'E/S directement du wiki Haskell.

350
Carsten

En Haskell, on utilise une construction appelée monad pour gérer les effets secondaires. Une monade signifie fondamentalement que vous encapsulez des valeurs dans un conteneur et que vous disposez de certaines fonctions pour chaîner des fonctions de valeurs à des valeurs à l'intérieur d'un conteneur. Si notre conteneur a le type:

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

nous pouvons implémenter en toute sécurité IO actions. Ce type signifie: Une action de type IO est une fonction qui prend un jeton de type RealWorld et renvoie un nouveau jeton, ainsi qu'un résultat.

L'idée sous-jacente est que chaque action IO mute l'état extérieur, représenté par le jeton magique RealWorld. En utilisant des monades, on peut chaîner plusieurs fonctions qui mutent le monde réel ensemble. La fonction la plus importante d’une monade est >>=, prononcé bind:

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

>>= effectue 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 putStrLn pour l’imprimer:

now >>= putStrLn

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

do currTime <- now
   putStrLn currTime

Tout cela est pur, car nous mappons la mutation et les informations sur le monde extérieur au jeton RealWorld. Ainsi, 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 jeton RealWorld est différent.

144
fuz

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

Dans des langages comme Haskell et Clean , qui sont purs, la situation est différente. En Haskell, l'heure actuelle ne serait pas disponible par le biais d'une fonction, mais d'une action dite IO, qui est la manière 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 types ferait en sorte que chaque valeur mondiale ne puisse ê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 time devrait être appelée avec un argument différent à chaque fois et serait donc autorisée à retourner une heure différente à chaque fois.

70
sepp2k

"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 heure.

49
Vlad Patryshev

Cela peut ê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 time ne renvoie pas simplement le temps, mais aussi la fonction que vous devez appeler pour obtenir la mesure du temps suivante.

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;
    }
}

(N'oubliez pas qu'il s'agit d'un exemple simple et peu pratique. En particulier, les nœuds de liste ne peuvent pas être récupérés car ils sont enracinés dans ProgramStartTime.)

Cette classe 'ClockStamp' agit comme une liste chaînée immuable, mais les nœuds sont générés à la demande et peuvent donc contenir l'heure 'actuelle'. Toute fonction qui souhaite mesurer l'heure doit avoir un paramètre 'clockStamp' et doit également renvoyer sa dernière mesure dans son résultat (pour que l'appelant ne voie 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, entrée et sortie, entrée et sortie. Il existe de nombreuses façons de masquer le passe-partout, en particulier au niveau de la conception linguistique. Je pense que Haskell utilise ce genre d’astuce puis cache les parties laides en utilisant des monades.

21
Craig Gidney

Je suis surpris qu’aucune des réponses ou des commentaires ne mentionne les coalgebras ou la coinduction. La coinduction est généralement mentionnée lors du raisonnement sur des structures de données infinies, mais elle est également applicable à un flux d'observations sans fin, tel qu'un registre de temps sur une CPU. Un charbon modele l'état caché; et les modèles de coinduction observant cet état. (Modèles à induction normale construisant un état .)

C'est un sujet d'actualité 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 p.)

16
Jeffrey Aguilera

Oui, il est possible pour une fonction pure de renvoyer l'heure, si cette heure lui est donnée en paramètre. Argument de temps différent, résultat de temps différent. Ensuite, formez également d’autres fonctions du temps et combinez-les avec un vocabulaire simple de fonctions (de temps) -transformantes (d’ordre supérieur). Puisque l'approche est sans état, le temps ici peut être continu (indépendant de la résolution) plutôt que discret, grandement renforcement de la modularité . Cette intuition est la base de la programmation réactive fonctionnelle (PRF).

12
Conal

Oui! Vous avez raison! Now () ou CurrentTime () ou toute méthode de signature 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 ne pas suivre la transparence référentielle. Mais le comportement réel de l'horloge système et de la fonction qui y est associée adhère à la transparence référentielle.

11
MduSenthil

Oui, une fonction get time peut exister dans la programmation fonctionnelle en utilisant une version légèrement modifiée de la programmation fonctionnelle appelée programmation fonctionnelle impure (la programmation par défaut ou principale est la programmation fonctionnelle pure).

Pour obtenir le temps (ou lire un fichier, ou lancer un missile), le code doit interagir avec le monde extérieur pour que le travail soit effectué et ce monde extérieur ne repose pas sur les fondements purs de la programmation fonctionnelle. Pour permettre à un monde de programmation purement fonctionnel d'interagir avec ce monde extérieur impur, les gens ont introduit la programmation fonctionnelle impure. Après tout, les logiciels qui n'interagissent pas avec le monde extérieur ne sont utiles que par des calculs mathématiques.

Peu de langages de programmation fonctionnels intègrent cette fonctionnalité d'impureté, de sorte qu'il n'est pas facile de séparer les codes impurs et ceux qui sont purs (comme F #, etc.) et certains langages de programmation fonctionnels garantissent 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 "world" qui présente l'état actuel du monde comme le temps, le nombre de personnes vivant dans le monde, etc. Puis obtenir le temps à partir de quel monde l'objet serait toujours pur, c'est-à-dire que vous passez dans le même état du monde que vous obtiendrez toujours le même temps.

11
Ankur

Votre question associe deux mesures d’un langage informatique: fonctionnel/impératif et pur/impur.

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

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 essentiellement 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, de sorte que vous ne sauriez jamais ce qu’ils ont calculé.

Pour être utile du tout, un programme doit être au moins un petit doigt impur. Une façon de rendre un programme pur utile est de le placer dans une mince enveloppe impure. 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
7
NovaDenizen

Vous abordez un sujet très important en programmation fonctionnelle, à savoir effectuer des E/S. De nombreuses langues pures utilisent des langages spécifiques à un domaine intégré, par exemple un sous-langage dont la tâche est de coder actions, qui peuvent avoir des résultats.

Le moteur d’exécution de Haskell, par exemple, attend de moi que je définisse une action appelée main 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 moteur d'exécution utilise les données calculées pour effectuer des E/S et les restitue 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 s'attendant à ce que le moteur d'exécution les exécute, le programmeur peut faire tout ce qu'un programme normal peut faire. Mais le système de types fort de Haskell crée une barrière solide 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 aboutit au résultat actuel. Temps processeur, puis transmettez le résultat à une autre action qui ajoute deux secondes et imprime le résultat. Écrire trop de programme est 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 nous pouvons savoir 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) en Haskell. L'opérateur >>= permet de composer des actions, en transmettant le résultat de la première à une fonction entraînant la deuxième action. Cela semble assez obscur, les compilateurs Haskell supportent le sucre syntaxique qui nous permet d’écrire ce 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?

2
MauganRa

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

Cela n'existe pas dans un sens purement fonctionnel.

Ou si non, alors comment peut-on connaître l'heure actuelle en programmation fonctionnelle?

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


Pour Haskell, il existe un concept d '' action IO 'qui représente un type qui peut être utilisé pour exécuter un processus IO. Ainsi, au lieu de référencer une valeur time, nous référons une valeur IO Time. Tout cela serait purement fonctionnel. Nous ne référençons pas time mais quelque chose du type 'lit la valeur du registre du temps'.

Lorsque nous exécutons réellement le programme Haskell, l'action IO aura effectivement lieu.

1
Chris Stryczynski