2017-07-22 1 views
0

Je suis l'exemple d'un livre pour implémenter une classe Steam en utilisant l'évaluation paresseuse dans Scala.Scala paresseux évaluation et appliquer la fonction

sealed trait Stream[+A] 
case object Empty extends Stream[Nothing] 
case class Cons[+A](h:() => A, t:() => Stream[A]) extends Stream[A] 

object Stream { 
    def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = { 
     lazy val head = hd 
     lazy val tail = tl 
     Cons(() => head,() => tail) 
    } 

    def empty[A]: Stream[A] = Empty 

    def apply[A](as: A*): Stream[A] = { 
     if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*)) 
    } 
} 

J'utilise une fonction simple pour tester si elle fonctionne

def printAndReturn: Int = { 
    println("called") 
    1 
} 

Je construis flux comme ce qui suit:

println(s"apply: ${ 
     Stream(
      printAndReturn, 
      printAndReturn, 
      printAndReturn, 
      printAndReturn 
     ) 
    }") 

La sortie est comme ceci:

called 
called 
called 
called 
apply: Cons(fpinscala.datastructures.Stream$$$Lambda$7/[email protected],fpinscala.datastructures.Stream$$$Lambda$8/[email protected]) 

Ensuite, je contre struits flux en utilisant cons:

println(s"cons: ${ 
    cons(
     printAndReturn, 
     cons(
      printAndReturn, 
      cons(printAndReturn, Empty) 
     ) 
    ) 
}") 

La sortie est:

cons: Cons(fpinscala.datastructures.Stream$$$Lambda$7/[email protected],fpinscala.datastructures.Stream$$$Lambda$8/[email protected]) 

Voici donc deux questions:

  1. Lors de la construction flux en utilisant la fonction appliquer, tous printAndReturn sont évalués. Est-ce parce que l'appel récursif à apply(as.head, ...) évalue chaque tête?
  2. Si la réponse à la première question est vraie, alors comment puis-je changer apply pour ne pas forcer l'évaluation?

Répondre

1
  1. Non Si vous mettez un point d'arrêt sur le println, vous trouverez que la méthode est en fait appelée lorsque vous créez le Stream. La ligne Stream(printAndReturn, ... appelle réellement votre méthode cependant beaucoup de fois vous l'avez mise là. Pourquoi? Considérez les signatures de type pour cons et apply:

    def cons[A](hd: => A, tl: => Stream[A]): Stream[A] 
    

    vs:

    def apply[A](as: A*): Stream[A] 
    

    Notez que la définition de cons a ses paramètres marqués comme => A. C'est un paramètre par nom. Déclarer une entrée comme celle-ci la rend paresseuse, retardant son évaluation jusqu'à ce qu'elle soit réellement utilisée. Par conséquent, votre println ne sera jamais appelé en utilisant cons. Comparez cela à apply. Vous n'utilisez pas de paramètre by name et, par conséquent, tout ce qui sera transmis à cette méthode sera automatiquement évalué.

  2. Malheureusement, il n'y a pas encore de solution super simple. Ce que vous voulez vraiment, c'est quelque chose comme def apply[A](as: (=>A)*): Stream[A] mais malheureusement, Scala ne supporte pas Vararg par les paramètres de nom. Voir this answer pour quelques idées sur la façon de contourner cela. Une façon consiste à simplement envelopper vos appels de fonction lors de la création du flux:

    Stream(
    () => printAndReturn, 
    () => printAndReturn, 
    () => printAndReturn, 
    () => printAndReturn) 
    

    Qui va alors retarder l'évaluation.

+0

Je vois maintenant. Ainsi, la solution de contournement que vous avez affichée passe une fonction qui renvoie la fonction à évaluer, de sorte que même la fonction() => est évaluée, func lui-même ne l'est pas. Est-ce correct? – KenKenKen