web-dev-qa-db-fra.com

Qu'est-ce qui remplace utilement les instructions if-then?

J'ai appris la programmation F # et fonctionnelle et essayé de faire les choses de manière fonctionnelle. Cependant, quand il s'agit de réécrire du code que j'avais déjà écrit en C #, je suis bloqué par de simples instructions if-then (celles qui ne font que quelque chose, ne renvoient pas de valeur). Je sais que vous pouvez retirer ceci en fa #:

if expr then do ()

Cependant, je pensais que c'était une approche impérative du codage? Peut-être que je n'ai pas assez appris sur la programmation fonctionnelle, mais elle ne me semble pas fonctionnelle. Je pensais que l'approche fonctionnelle consistait à composer des fonctions et des expressions, pas simplement à exécuter des instructions les unes après les autres, ce qui semble être si encourageant.

Alors, est-ce que je manque quelque chose et si-alors tout va bien dans le monde fonctionnel? Sinon, quel est l'équivalent fonctionnel d'une telle déclaration? Comment pourrais-je prendre un si-alors et le rendre fonctionnel?

Edit: J'ai peut-être posé la mauvaise question (désolé, la programmation fonctionnelle est encore relativement nouvelle): prenons un exemple concret qui m'a même amené à demander ceci:

if not <| System.String.IsNullOrWhiteSpace(data) then do
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)
    req.ContentLength <- int64 byteData.Length
    let postStream : System.IO.Stream = req.GetRequestStream()
    postStream.Write(byteData, 0, byteData.Length)
    postStream.Flush()
    postStream.Dispose()

Le corps de ce si-alors ne renvoie rien, mais je ne sais pas comment je pourrais le rendre plus fonctionnel (si c'est même possible). Je ne connais pas la technique appropriée pour minimiser le code impératif. Étant donné la nature de F #, il est assez facile de transporter mon C # directement, mais j'ai des difficultés à le rendre fonctionnel. Chaque fois que j'atteins une telle déclaration if en C # et que j'essaie de la transférer en F #, je me décourage de ne pas trouver un moyen de rendre le code plus fonctionnel.

36
Bob

Un point important qui n’a pas encore été mentionné est la différence entre if .. then .. else et if .. then sans la branche else.

If en langages fonctionnels

L’interprétation fonctionnelle de if est qu’il s’agit d’une expression qui a une valeur. Pour évaluer la valeur de if c then e1 else e2, vous évaluez la condition c, puis évaluez e1 ou e2, en fonction de la condition. Cela vous donne le résultat du if .. then .. else.

Si vous avez juste if c then e, vous ne saurez pas quel devrait être le résultat de l'évaluation si c est false, car il n'y a pas de branche else! Ce qui suit n'a clairement aucun sens:

let num = if input > 0 then 10

En F #, les expressions ayant des effets secondaires tels que printf "hi" renvoient une valeur spéciale de type unit. Le type n'a qu'une seule valeur (écrite en tant que ()) et vous pouvez donc écrire if qui produit un effet dans un seul cas:

let u = if input > 0 then printf "hi" else ()

Ceci est toujours évalué à unit, mais dans la branche true, il effectue également l'effet secondaire. Dans la branche false, il retourne simplement une valeur unit. En fa #, vous n'avez pas à écrire le bit else () à la main, mais conceptuellement, il est toujours là. Tu peux écrire:

let u = if input > 0 then printfn "hi"

Concernant votre exemple supplémentaire

Le code me convient parfaitement. Lorsque vous devez gérer des API impératives (comme beaucoup de bibliothèques .NET), la meilleure option consiste à utiliser les fonctionnalités impératives telles que if avec une branche unit- return.

Vous pouvez utiliser différents réglages, comme représenter vos données en utilisant option<string> (au lieu de simplement string avec null ou une chaîne vide). De cette façon, vous pouvez utiliser None pour représenter les données manquantes et toute autre chose constituerait une entrée valide. Vous pouvez ensuite utiliser certaines fonctions d'ordre supérieur pour utiliser des options, telles que Option.iter, qui appelle une fonction donnée s'il existe une valeur:

maybeData |> Option.iter (fun data ->
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)  
    req.ContentLength <- int64 byteData.Length  
    use postStream = req.GetRequestStream()  
    postStream.Write(byteData, 0, byteData.Length) )

Ce n'est pas vraiment moins impératif, mais c'est plus déclaratif, car vous n'avez pas à écrire vous-même la if. BTW: Je recommande également d'utiliser use si vous voulez Dispose objet auotmatically.

33
Tomas Petricek

Il n'y a rien de mal à si-alors dans le monde fonctionnel.

Votre exemple est en fait similaire à let _ = expr puisque expr a des effets secondaires et nous ignorons sa valeur de retour. Un exemple plus intéressant est:

if cond then expr

qui équivaut à:

match cond with
| true -> expr
| false -> ()

si nous utilisons un filtrage par motif. 

Lorsque la condition est simple ou qu'il n'y a qu'une seule expression conditionnelle, if-then est plus lisible que la recherche de modèle. De plus, il est intéressant de noter que tout dans la programmation fonctionnelle est l'expression. Donc, if cond then expr est en réalité le raccourci de if cond then expr else ()

Si alors elle-même n’est pas impérative, l’utilisation de if-then comme déclaration est un mode de pensée impératif. D'après mon expérience, la programmation fonctionnelle concerne davantage la manière de penser que les flux de contrôle concrets dans les langages de programmation. 

MODIFIER:

Votre code est totalement lisible. Certains points mineurs se débarrassent des mots-clés do, des annotations de type et postStream.Dispose() (en utilisant le mot-clé use) redondants:

if not <| System.String.IsNullOrWhiteSpace(data) then
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)
    req.ContentLength <- int64 byteData.Length
    use postStream = req.GetRequestStream()
    postStream.Write(byteData, 0, byteData.Length)
    postStream.Flush()
13
pad

Ce n'est pas le if-expression qui est impératif, c'est ce qui va dans le if-expression. Par exemple, let abs num = if num < 0 then -num else num est une manière totalement fonctionnelle d’écrire la fonction abs. Il faut un argument et retourne une transformation de cet argument sans effets secondaires. Mais quand vous avez "un code qui ne fait que quelque chose, pas une valeur," alors vous écrivez quelque chose qui n'est pas purement fonctionnel. Le but de la programmation fonctionnelle est de minimiser la partie de votre programme qui peut être décrite de cette façon. La façon dont vous écrivez vos conditions est tangentielle.

7
Chuck

Pour écrire du code complexe, vous devez créer une branche à un moment donné. Il existe un nombre très limité de façons de le faire, et toutes nécessitent un flux logique dans une section de code. Si vous voulez éviter d'utiliser si/alors/sinon, il est possible de tricher avec loop/while/repeat - mais cela rendra votre code beaucoup moins raisonnable à maintenir et à lire.

La programmation fonctionnelle ne signifie pas que vous ne devriez pas exécuter les instructions les unes après les autres - cela signifie simplement que vous ne devriez pas avoir un état mutable. Chaque fonction doit se comporter de manière fiable chaque fois qu’elle est appelée. Toute différence dans la manière dont les données sont traitées doit être prise en compte par les données transmises, au lieu d'un déclencheur qui est masqué à celui qui appelle la fonction.

Par exemple, si nous avons une fonction foo(int, bool) qui renvoie quelque chose de différent selon que bool est vrai ou faux, il y aura presque certainement une instruction if quelque part dans foo(). C'est parfaitement légitime. Ce qui n'est PAS légitime, c'est d'avoir une fonction foo(int) qui retourne quelque chose de différent selon que c'est la première fois qu'elle est appelée dans le programme. C’est une fonction «dynamique» qui rend la vie difficile à quiconque maintient le programme.

6
Michael Martin

Deux observations peuvent faciliter la transition de la programmation impérative à la programmation fonctionnelle ("tout est une expression"):

  1. unit est une valeur, alors qu'une expression renvoyant void en C # est traitée comme une instruction. Autrement dit, C # fait la distinction entre les déclarations et les expressions. En F # tout est une expression.

  2. En C #, les valeurs peuvent être ignorées. en F #, ils ne peuvent pas, fournissant ainsi un plus haut niveau de sécurité de type. Cette clarté rend les programmes F # plus faciles à raisonner et offre de meilleures garanties.

3
Daniel

Toutes mes excuses pour ne pas savoir F #, mais voici une solution possible en javascript:

function $if(param) {
    return new Condition(param)
}

function Condition(IF, THEN, ELSE) {
    this.call = function(seq) {
        if(this.lastCond != undefined) 
            return this.lastCond.call(
                sequence(
                    this.if, 
                    this.then, 
                    this.else, 
                    (this.elsif ? this.elsif.if : undefined),
                    seq || undefined
                )
            );
         else 
            return sequence(
                this.if, 
                this.then, 
                this.else, 
                (this.elsif ? this.elsif.if : undefined),
                seq || undefined
            )
    }


    this.if   = IF ? IF : f => { this.if = f; return this };
    this.then = THEN ? THEN : f => { this.then = f; return this };
    this.else = ELSE ? ELSE : f => { this.else = f; return this };
    this.elsif = f => {this.elsif = $if(f); this.elsif.lastCond = this; return this.elsif};
}

function sequence(IF, THEN, ELSE, ELSIF, FINALLY) {
    return function(val) {
        if( IF(val) ) 
            return THEN();

        else if( ELSIF && ELSIF(val) ) 
            return FINALLY(val);

        else if( ELSE ) 
            return ELSE();

        else 
            return undefined

    }
}}

La fonction $ if renvoie un objet avec la construction if..then..else..elsif à l'aide du constructeur de condition . Une fois que vous avez appelé Condition.elsif (), vous allez créer un autre objet Condition, créant essentiellement une liste liée qui peut être parcouru de manière récursive en utilisant sequence ()

Vous pourriez l'utiliser comme:

var eq = val => x => x == val ? true : false;

$if( eq(128) ).then( doStuff ).else( doStuff )
.elsif( eq(255) ).then( doStuff ).else( doStuff ).call();

Cependant, je me rends compte que l'utilisation d'un objet n'est pas une approche purement fonctionnelle. Donc, dans ce cas, vous pouvez renoncer à l'objet dans son ensemble:

sequence(f, f, f, f,
    sequence(f, f, f, f
        sequence(f, f, f)
    )
);

Vous pouvez voir que la magie est vraiment dans la fonction sequence (). Je ne vais pas essayer de répondre à votre question spécifique en javascript. Mais je pense que l'essentiel est que vous deviez créer une fonction qui exécutera des fonctions arbitraires sous une instruction if..then, puis imbriquera des multiples de cette fonction en utilisant la récursivité pour créer une chaîne logique complexe. Au moins, comme ça tu n'auras pas à te répéter;) 

Ce code est juste un prototype, merci de le prendre comme preuve de concept et de me laisser savoir si vous trouvez des bugs.

0
Duco