Je dois attribuer une valeur à une variable basée sur une condition. Je veux le faire avec un paradigme de programmation fonctionnel à l'esprit, donc je ne peux pas le déclarer dans la portée externe puis le réaffecter.
if(condition) { const foo = 1 do_stuff() } else { const foo = 0 do_other_stuff() } do_third_stuff(foo)
Je sais, je peux utiliser une expression ternaire ici comme
const foo = condition? 1: 0
mais que faire si j'ai une autre routine à faire dans ma condition, comme:
// let foo = undefined // no-let! if(condition) { const foo = 1 } else { const foo = 0 } do_some_stuff(foo) // Uncaught ReferenceError: foo is not defined
3 Réponses :
Rien ne vous empêche de séparer les deux:
let isFoo = expensiveIsFooMethod(); const foo = isFoo ? 1 : 0; if(isFoo) { do_stuff(); } else { do_other_stuff(); } do_third_stuff(foo);
Dans le cas où la condition
est une exécution coûteuse, affectez-la simplement à une variable avant de l'utiliser plusieurs fois:
const foo = condition ? 1 : 0; if(condition) { do_stuff(); } else { do_other_stuff(); } do_third_stuff(foo);
Vous avez raison de dire que ce serait plus propre si vous n'aviez pas à répéter la condition, mais vous avez introduit cette limitation car vous utilisez un const
qui rend impossible d'attribuer une valeur à votre const
à plus d'un endroit.
Je suggère de l'emporter sur les deux options ici. Qu'est-ce qui compte le plus pour vous: une syntaxe plus claire ou vous assurer de ne jamais écraser la valeur?
Voir le commentaire de @ reify sur la question
Parce que vous ne voulez pas déclarer fou à l'extérieur, pourquoi vous ne le faites pas simplement de cette façon:
if(condition) { const foo = 1 do_stuff() do_third_stuff(foo) } else { const foo = 0 do_other_stuff() do_third_stuff(foo) }
Surtout si do_third_stuff ()
est plus d'une ligne, cela viole DRY et peut conduire à un code difficile à maintenir, ce qui à son tour conduit à une forte probabilité de bogues lors de l'introduction de changements (par exemple, en changeant le code < > if mais en oubliant de changer également le else
)
Vous encoderiez probablement votre cas en utilisant un type de données algébrique (ADT) comme Soit
. Autrement dit, vous pouvez couvrir deux sous-cas: gauche et droite .
Voir le code à partir de // -> La solution commence ici
et plus . Le code précédent est une mini bibliothèque FP standard utilisant du JavaScript vanilla pour rendre le code exécutable. Vérifiez-le et profitez-en!
// Mini standard library // ------------------------------- // The identity combinator // I :: a -> a const I = x => x // Pipes many unary functions // // pipe :: [a -> b] -> a -> c const pipe = xs => x => xs.reduce ((o, f) => f (o), x) // Either ADT const Either = (() => { // Creates an instance of Either.Right // // of :: b -> Either a b const of = x => ({ right: x }) // Creates an instance of Either.Right // // Right :: b -> Either a b const Right = of // Creates an instance of Either.Left // // Left :: a -> Either a b const Left = x => ({ left: x }) // Maps Either.Left or Either.Right in a single operation // // bimap :: (a -> c) -> (b -> d) -> Either a b -> Either c -> d const bimap = f => g => ({ left, right }) => left ? Left (f (left)) : Right (g (right)) // Lifts a value to Either based on a condition, where false // results in Left, and true is Right. // // tagBy :: (a -> Boolean) -> a -> Either a a const tagBy = f => x => f (x) ? Right (x) : Left (x) // Unwraps Either.Left or Either.Right with mapping functions // // either :: (a -> c) -> (b -> c) -> Either a b -> c const either = f => g => ({ left, right }) => left ? f (left) : g (right) // Unwraps Either.Left or Either.Right and outputs the raw value on them // // unwrap :: Either a b -> c const unwrap = either (I) (I) return { of, Right, Left, bimap, tagBy, either, unwrap } }) () // --> Solution starts here // doStuff :: Number -> Number const doStuff = x => x + 1 // doStuff2 :: Number -> Number const doStuff2 = x => x * 4 const { tagBy, bimap, unwrap } = Either // doStuff3 :: Number -> Number const doStuff3 = pipe ([ tagBy (x => x > 3), bimap (doStuff) (doStuff2), // <-- here's the decision! unwrap ]) const output1 = doStuff3 (2) const output2 = doStuff3 (30) console.log ('output1: ', output1) console.log ('output2: ', output2)
Maintenant, j'introduis pipe
pour coller une composition d'une ou plusieurs fonctions unaires, ce qui rend le code plus élégant:
// Mini standard library // ------------------------------- // The identity combinator // I :: a -> a const I = x => x // Either ADT const Either = (() => { // Creates an instance of Either.Right // // of :: b -> Either a b const of = x => ({ right: x }) // Creates an instance of Either.Right // // Right :: b -> Either a b const Right = of // Creates an instance of Either.Left // // Left :: a -> Either a b const Left = x => ({ left: x }) // Maps Either.Left or Either.Right in a single operation // // bimap :: (a -> c) -> (b -> d) -> Either a b -> Either c -> d const bimap = f => g => ({ left, right }) => left ? Left (f (left)) : Right (g (right)) // Lifts a value to Either based on a condition, where false // results in Left, and true is Right. // // tagBy :: (a -> Boolean) -> a -> Either a a const tagBy = f => x => f (x) ? Right (x) : Left (x) // Unwraps Either.Left or Either.Right with mapping functions // // either :: (a -> c) -> (b -> c) -> Either a b -> c const either = f => g => ({ left, right }) => left ? f (left) : g (right) // Unwraps Either.Left or Either.Right and outputs the raw value on them // // unwrap :: Either a b -> c const unwrap = either (I) (I) return { of, Right, Left, bimap, tagBy, either, unwrap } }) () // --> Solution starts here // Lifts to Either.Right if x is greater than 3, // otherwise, x is encoded as Left. // // tagGt3 :: Number -> Either Number Number const tagGt3 = Either.tagBy (x => x > 3) // doStuff :: Number -> Number const doStuff = x => x + 1 // doStuff2 :: Number -> Number const doStuff2 = x => x * 4 // doStuff3 :: Either Number Number -> Either Number Number const doStuff3 = Either.bimap (doStuff) (doStuff2) // <-- here's the decision! const eitherValue1 = doStuff3 (tagGt3 (2)) const eitherValue2 = doStuff3 (tagGt3 (30)) const output1 = Either.unwrap (eitherValue1) const output2 = Either.unwrap (eitherValue2) console.log ('output1: ', output1) console.log ('output2: ', output2)
Dernièrement, j'ai tendance à être aussi explicite que possible avec les conditions en les enveloppant simplement dans des fonctions: const _let = f => f (); _let ((x = someParentScopeVar) => {if (x) return doStuff (x); else return doOtherStuff (x)})
. En abusant des paramètres par défaut de cette façon, le flux de données est explicite. De plus, si au lieu de someParentScopeVar
vous passez une expression extensive dont le résultat est utilisé plusieurs fois, vous obtenez une sorte de liaison let
.
@reify Ce commentaire est-il au bon endroit? Peut-être que vous vouliez ajouter cette information à la question d'OP: O
Le commentaire @reify est un peu hors sujet pour cet article, mais je pense aussi que c'est une bonne utilisation des arguments par défaut. Je suggère bind
, where
ou assign
comme noms alternatifs pour _let
.
@reify Maintenant, je me demande si cela devrait être intégré à la boucle
/ recur
que j'écris souvent. Je commence à ressembler beaucoup au named-let de Racket et c'est vraiment une bonne chose :RÉ
@ user633183 Merci! J'essaye de divulguer FP et de me défier: D
@ user633183 J'ai écrit ce commentaire parce que je pense que le type de base Either
est principalement utilisé à cause de sa contrainte de monade, où left
indique une erreur, pas pour encoder des branches conditionnelles. Quoi qu'il en soit, merci pour le lien de raquette. Je vais l'examiner.
@reify Pas vraiment. Soit
peut être utilisé pour quitter une composition Kleisli à un certain point en sortie Gauche
, et continuer avec Droite
.
Correct, utiliser Soit
pour signaler des erreurs n'est qu'un un cas d'utilisation possible (mais populaire).
@ user633183 Ouais.
@ user633183 De plus, un cas réel n'impliquerait probablement pas l'extraction de la valeur de Soit
comme dans mon exemple. La composition entière consisterait à transformer de / en Soit
et d'autres types de données ... Dans un cas très simple comme les OP, cela pourrait sembler exagéré, mais je suppose que l'OP et les futurs lecteurs pourraient Prenez l'astuce pour produire des solutions plus complexes avec Soit
.
Qu'en est-il de la composition fonctionnelle si vous désirez un style fonctionnel?
fournir un exemple plz
Vous êtes toujours coincé dans une réflexion impérative. Vos fonctions ne reçoivent ni ne retournent de valeur. Ils sont complètement inutiles. La programmation fonctionnelle ne consiste pas seulement à utiliser des fonctions, mais à les utiliser de manière pure. Une fonction pure prend une valeur et renvoie une valeur transformée. Rien d'autre. Ensuite, vous reconnaîtrez que vous pouvez composer de telles fonctions pures, ce qui réduit considérablement le besoin de valeurs intermédiaires affectées aux variables.
Que sont censés fournir
do_stuff
etdo_other_stuff
? Il ne renvoie pas une valeur que vous utilisez, il s'agit donc d'effets secondaires ou de code mort. Si ce sont des effets secondaires, cela n'aide pas à changer unlet
enconst
@reify devrait penser à une solution comme ma réponse récemment ajoutée.