33 votes

Trouvez-vous toujours besoin de variables que vous pouvez modifier, et si oui pourquoi?

L'un des arguments que j'ai entendu à l'encontre de langages fonctionnels est que seule la cession de codage est trop difficile, ou, au moins, nettement plus que "normal" de la programmation.

Mais la recherche par le biais de mon code, j'ai réalisé que je n'ai vraiment pas beaucoup (tout?) les profils d'utilisation qui ne peuvent pas être écrits aussi bien en utilisant un seul formulaire de cession, si vous écrivez dans un assez moderne de la langue.

Quels sont donc les cas d'utilisation pour des variables qui varient à l'intérieur d'un seul appel de leur portée? En gardant à l'esprit que les indices de boucles, paramètres, et d'autres de la portée lié valeurs qui varient entre les invocations ne sont pas des missions multiples dans ce cas (sauf si vous avez à modifier dans le corps pour une raison quelconque), et en supposant que vous écrivez dans quelque chose d'un assez loin au-dessus de l'assemblée niveau de langue, où vous pouvez écrire des choses comme

values.sum

ou (dans le cas où la somme n'est pas fourni)

function collection.sum --> inject(zero, function (v,t) --> t+v )

et

x = if a > b then a else b

ou

n = case s 
  /^\d*$/ : s.to_int
  ''      : 0
  '*'     : a.length
  '?'     : a.length.random
  else    fail "I don't know how many you want"

lorsque vous en avez besoin, et ont interprétations de la liste, la carte/recueillir, et ainsi de suite disponible.

Trouvez-vous que vous avez encore envie/besoin mutable variables dans un tel environnement, et, dans l'affirmative, à quelles fins?

Pour être clair, je ne suis pas à demander une récitation des objections à l'encontre de la SSA de la forme, mais plutôt des exemples concrets où ces objections s'appliquerait. Je suis à la recherche de morceaux de code qui sont clairs et concis avec des variables mutables et n'a pas pu être écrite sans eux.

Mes exemples préférés à ce jour (et le meilleur objection que j'attends d'eux):

  1. Paul Johnson de Fisher-Yates algorithme de réponse, ce qui est assez solide lorsque vous incluez le big-O contraintes. Mais alors, que catulahoops points, le big-O problème n'est pas lié à la SSA question, mais plutôt d'avoir mutable types de données, et avec qui mettre de côté l'algorithme peut être écrit assez clairement dans l'afrique subsaharienne:

     shuffle(Lst) ->
         array:to_list(shuffle(array:from_list(Lst), erlang:length(Lst) - 1)).
     shuffle(Array, 0) -> Array;
     shuffle(Array, N) ->
         K = random:uniform(N) - 1,
         Ek = array:get(K, Array),
         En = array:get(N, Array),
         shuffle(array:set(K, En, array:set(N, Ek, Array)), N-1).
    
  2. jpalecek de la surface d'un polygone exemple:

    def area(figure : List[Point]) : Float = {
      if(figure.empty) return 0
      val last = figure(0)
      var first= figure(0)
      val ret = 0
      for (pt <- figure) {
        ret+=crossprod(last - first, pt - first)
        last = pt
      }
      ret
    }
    

    qui pourrait encore être écrit quelque chose comme:

    def area(figure : List[Point]) : Float = {
        if figure.length < 3
            0
          else
            var a = figure(0)
            var b = figure(1)
            var c = figure(2)
            if figure.length == 3
                magnitude(crossproduct(b-a,c-a))
              else 
                foldLeft((0,a,b))(figure.rest)) { 
                   ((t,a,b),c) => (t+area([a,b,c]),a,c)
                   }
    

    Ou, puisque certaines personnes objet de la densité de cette formulation, il pourrait être reformulée:

    def area([])    = 0.0   # An empty figure has no area
    def area([_])   = 0.0   # ...nor does a point
    def area([_,_]) = 0.0   # ...or a line segment
    def area([a,b,c]) =     # The area of a triangle can be found directly
        magnitude(crossproduct(b-a,c-a))
    def area(figure) =      # For larger figures, reduce to triangles and sum
        as_triangles(figure).collect(area).sum
    
    
    def as_triangles([])      = []  # No triangles without at least three points
    def as_triangles([_])     = []
    def as_triangles([_,_])   = []
    def as_triangles([a,b,c | rest) = [[a,b,c] | as_triangles([a,c | rest])]
    
  3. La princesse est point à propos de la difficulté de la mise en œuvre de O(1) les files d'attente avec des structures immuables qui est intéressant (et peut fournir la base pour un exemple éloquent), mais comme l'a dit il est fondamentalement sur les transformations de la structure de données, et non pas directement sur l'affectation multiple question.

  4. Je suis intrigué par le Crible d'Eratosthène réponse, mais pas convaincu. Le bon big-O, tirez autant de primes que vous le souhaitez générateur de donnée dans le document qu'il cite n'a pas l'air facile à mettre en œuvre correctement avec ou sans SSA.


Et bien, merci tout le monde pour essayer. Comme la plupart des réponses s'est avéré être soit 1) sur la base de données mutable structures, et non pas sur une seule affectation, et 2) dans la mesure où ils étaient sur le point d'attribution unique forme facilement contrés par des praticiens qualifiés dans l'art, je vais frapper la ligne de mes propos et / ou restructurer (peut-être l'avez dans la sauvegarde comme un sujet de discussion dans le cas, peu probable, je n'ai plus de mots avant que j'ai le temps).

Merci encore.

18voto

Paul Johnson Points 8604

Le problème le plus difficile que j'ai rencontré est de réarrangement d'une liste. Le Fisher-Yates algorithme (connu également sous le nom de l'algorithme de Knuth) consiste à itérer sur la liste de permutation de chaque élément par un autre élément aléatoire. L'algorithme est O(n), bien connu et depuis longtemps prouvé correct (une propriété importante dans certaines applications). Mais il nécessite mutable tableaux.

Ce n'est pas à dire que vous ne pouvez pas faire traînant dans un programme fonctionnel. Oleg Kiselyov a écrit sur ce sujet. Mais si j'ai bien compris, fonctionnelle brassage est O(n . log n), car il fonctionne par la construction d'un arbre binaire.

Bien sûr, si j'avais besoin d'écrire le Fisher-Yates algorithme en Haskell je venais de mettre dans le ST monade, qui vous permet d'emballer un algorithme impliquant mutable tableaux à l'intérieur d'une belle pure fonction, comme ceci:

-- | Implementation of the random swap algorithm for shuffling.  Reads a list
-- into a mutable ST array, shuffles it in place, and reads out the result
-- as a list.

module Data.Shuffle (shuffle) where


import Control.Monad
import Control.Monad.ST
import Data.Array.ST
import Data.STRef
import System.Random

-- | Shuffle a value based on a random seed.
shuffle :: (RandomGen g) => g -> [a] -> [a]
shuffle _ [] = []
shuffle g xs = 
    runST $ do
      sg <- newSTRef g
      let n = length xs
      v <- newListArray (1, n) xs
      mapM_ (shuffle1 sg v) [1..n]
      getElems v

-- Internal function to swap element i with a random element at or above it.
shuffle1 :: (RandomGen g) => STRef s g -> STArray s Int a -> Int -> ST s ()
shuffle1 sg v i = do
  (_, n) <- getBounds v
  r <- getRnd sg $ randomR (i, n)
  when (r /= i) $ do
    vi <- readArray v i
    vr <- readArray v r
    writeArray v i vr
    writeArray v r vi


-- Internal function for using random numbers
getRnd :: (RandomGen g) => STRef s g -> (g -> (a, g)) -> ST s a
getRnd sg f = do
  g1 <- readSTRef sg
  let (v, g2) = f g1
  writeSTRef sg g2
  return

v

15voto

Jason Cohen Points 36475

Si vous voulez faire de l'argument théorique, alors bien sûr, il n'est pas techniquement nécessaire pour affecter une variable plus d'une fois. La preuve en est que tout le code peut être représenté de la SSA (une Seule allocation Statique) forme. En effet, c'est la forme la plus utile pour de nombreux types d'analyse statique et dynamique.

Dans le même temps, il y a des raisons que nous n'avons pas tous écrire du code en afrique subsaharienne forme pour commencer:

  1. Il faut généralement plus d'états (ou plus de lignes de code) pour écrire le code de cette façon. La brièveté a de la valeur.
  2. C'est presque toujours de moins en moins efficace. Oui, je sais que vous parlez de la hausse des langues -- une juste détermination de la portée-mais même dans le monde de Java et C#, loin de l'assemblée, la vitesse de questions. Il existe quelques applications où la vitesse n'est pas pertinent.
  3. Il n'est pas facile à comprendre. Bien que l'afrique subsaharienne est plus "simple" dans un sens mathématique, c'est plus abstraite du sens commun, qui est tout ce qui compte dans le vrai monde de la programmation. Si vous avez d'être vraiment intelligent de grok, alors il n'a pas sa place dans la programmation en général.

Même dans vos exemples ci-dessus, il est facile de faire des trous. Prenez votre case déclaration. Que faire si il ya une option administrative qui détermine si '*' est autorisée, et une autre pour s' '?' est-elle autorisée? Aussi, le zéro n'est pas admis pour l'entier litige, à moins que l'utilisateur dispose d'un système d'autorisation qui permet.

C'est un exemple plus réaliste avec des branches et des conditions. Pourriez-vous écrire cela comme une simple "déclaration?" Si c'est votre "déclaration" vraiment différent de la plupart des états distincts? Si non, combien temporaire écriture seules les variables dont vous avez besoin? Et est-ce que la situation nettement mieux que d'avoir une seule variable?

12voto

Norman Ramsey Points 115730

Je n'ai jamais identifié de tels cas. Et tandis que vous pouvez toujours inventer de nouveaux noms, comme dans la conversion à l'afrique subsaharienne forme, en fait, je trouve que c'est facile et naturel pour chaque valeur de disposer de son propre nom. Un des langages comme Haskell me donne beaucoup de choix sur les valeurs de nom, et à deux endroits différents pour mettre le nom de liaisons (let et where). Je trouve l'unique-le formulaire de cession du bien naturel et pas du tout difficile.

Je ne le manquez de temps en temps être en mesure d'avoir des pointeurs vers des objets mutables sur le tas. Mais ces choses n'ont pas de nom, il n'est donc pas la même objection. (Et je trouve aussi que lorsque j'utilise mutable objets sur le tas, j'ai tendance à écrire plus de bugs!)

6voto

Juliet Points 40758

Je pense que vous trouverez les plus productives de langues vous permettent de mélanger fonctionnel et impératif de styles, comme OCaml et F#.

Dans la plupart des cas, je peux écrire du code qui est tout simplement une longue lignée de "carte de x à y, de réduire de y à z". Dans 95% des cas, la programmation fonctionnelle simplifie mon code, mais il est un domaine où l'immuabilité montre ses dents:

La grande disparité entre la facilité de mise en œuvre et immuable de la pile et immuable de la file d'attente.

Les piles sont faciles et maille bien avec de la persévérance, les files d'attente sont ridicules.

Le plus commun des implémentations de immuable files d' utiliser un ou plusieurs internes piles pile et de rotations. L'avantage est que ces files d'attente s'exécuter en O(1) la plupart du temps, mais certaines opérations exécuter en O(n). Si vous êtes en s'appuyant sur la persistance dans votre application, puis son possible, en principe, que chaque opération s'exécute en O(n). Ces files d'attente ne sont pas bons quand vous en avez besoin en temps réel (ou au moins compatibles) de la performance.

Chris Okasaki fournit une implémentation de immuable des files d'attente dans son livre, ils utilisent la paresse à atteindre O(1) pour toutes les opérations. Son un de très intelligent, raisonnable concis de la mise en œuvre d'une file d'attente en temps réel -- mais il nécessite une compréhension profonde de ses sous-jacent détails de mise en œuvre, et de son encore un ordre de grandeur plus complexe qu'une immuable de la pile.

En contraste, je peux écrire une pile et file d'attente à l'aide mutable listes chaînées qui s'exécutent en temps constant pour toutes les opérations, ainsi que le code résultant serait très simple.


Concernant l'aire d'un polygone, il est facile de le convertir en une forme fonctionnelle. Imaginons que nous avons un Vecteur de module comme ceci:

module Vector =
    type point =
        { x : float; y : float}
        with
            static member ( + ) ((p1 : point), (p2 : point)) =
                { x = p1.x + p2.x;
                  y = p1.y + p2.y;}

            static member ( * ) ((p : point), (scalar : float)) =
                { x = p.x * scalar;
                  y = p.y * scalar;}

            static member ( - ) ((p1 : point), (p2 : point)) = 
                { x = p1.x - p2.x;
                  y = p1.y - p2.y;}

    let empty = { x = 0.; y = 0.;}
    let to_tuple2 (p : point) = (p.x, p.y)
    let from_tuple2 (x, y) = { x = x; y = y;}
    let crossproduct (p1 : point) (p2 : point) =
        { x = p1.x * p2.y; y = -p1.y * p2.x }

Nous pouvons définir notre fonction à l'aide d'un peu de tuple de la magie:

let area (figure : point list) =
    figure
    |> Seq.map to_tuple2
    |> Seq.fold
        (fun (sum, (a, b)) (c, d) -> (sum + a*d - b*c, (c, d) ) )
        (0., to_tuple2 (List.hd figure))
    |> fun (sum, _) -> abs(sum) / 2.0

Ou nous pouvons utiliser le produit au lieu de la croix

let area2 (figure : point list) =
    figure
    |> Seq.fold
        (fun (acc, prev) cur -> (acc + (crossproduct prev cur), cur))
        (empty, List.hd figure)
    |> fun (acc, _) -> abs(acc.x + acc.y) / 2.0

Je ne trouve pas que ce soit la fonction illisible.

4voto

cthulahoops Points 2626

Que de lecture aléatoire de l'algorithme est trivial à implémenter à l'aide d'attribution unique, en fait c'est exactement le même que l'impératif de la solution à l'itération réécrit à la queue de la récursivité. (Erlang, parce que je peux écrire plus rapidement que Haskell.)

 shuffle(Lst) ->
     array:to_list(shuffle(array:from_list(Lst), erlang:length(Lst) - 1)).

 shuffle(Array, 0) -> Array;
 shuffle(Array, N) ->
     K = random:uniform(N) - 1,
     Ek = array:get(K, Array),
     En = array:get(N, Array),
     shuffle(array:set(K, En, array:set(N, Ek, Array)), N-1).

Si l'efficacité de ces opérations de matrice est un sujet de préoccupation, alors que c'est une question sur mutable structures de données et n'a rien à voir avec affectation unique.

Vous n'obtiendrez pas de réponse à cette question, car aucun des exemples existent. C'est seulement une question de familiarité avec ce style.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X