Question

Je viens de terminer Programmation à Scala , et je l'ai été regardant dans les changements entre Scala 2.7 et 2.8. Celui qui semble être le plus important est le plugin continuations, mais je ne comprends pas ce qu'il est utile ou comment il fonctionne. Je l'ai vu que ce qui est bon pour les E / S asynchrone, mais je ne l'ai pas été en mesure de savoir pourquoi. Une partie des ressources les plus populaires sur le sujet sont les suivants:

Et cette question sur Stack Overflow:

Malheureusement, aucune de ces références essayer de définir ce que sont continuations ou ce que le changement / de remise à zéro sont censés faire, et je n'ai pas trouvé de références qui le font. Je ne l'ai pas été en mesure de deviner comment l'un des exemples les articles liés au travail (ou ce qu'ils font), donc une façon de me aider pourrait être d'aller en ligne par ligne par l'un de ces échantillons. Même ce simple du troisième article:

reset {
    ...
    shift { k: (Int=>Int) =>  // The continuation k will be the '_ + 1' below.
        k(7)
    } + 1
}
// Result: 8

Pourquoi le résultat 8? Ce serait sans doute me aider à démarrer.

Autres conseils

J'ai trouvé les explications existantes à être moins efficaces pour expliquer le concept que je l'espère. J'espère que celui-ci est claire (et correcte.) Je ne l'ai pas encore utilisé continuations.

Lorsqu'une fonction de continuation cf est appelée:

  1. saute d'exécution sur le reste du bloc shift et commence à nouveau à la fin de celui-ci
    • le paramètre passé à cf est ce que le bloc de shift « correspond » à l'exécution en continue. cela peut être différent pour chaque appel à cf
  2. L'exécution se poursuit jusqu'à la fin du bloc reset (ou jusqu'à ce qu'un appel à reset s'il n'y a pas de bloc)
    • le résultat du bloc reset (ou le paramètre à reset () s'il n'y a pas de bloc) est ce que cf retourne
  3. L'exécution se poursuit après cf jusqu'à la fin du bloc shift
  4. L'exécution saute jusqu'à la fin du bloc reset (ou un appel à réinitialiser?)

Ainsi, dans cet exemple, suivez les lettres de A à Z

reset {
  // A
  shift { cf: (Int=>Int) =>
    // B
    val eleven = cf(10)
    // E
    println(eleven)
    val oneHundredOne = cf(100)
    // H
    println(oneHundredOne)
    oneHundredOne
  }
  // C execution continues here with the 10 as the context
  // F execution continues here with 100
  + 1
  // D 10.+(1) has been executed - 11 is returned from cf which gets assigned to eleven
  // G 100.+(1) has been executed and 101 is returned and assigned to oneHundredOne
}
// I

Cette affiche:

11
101

Étant donné l'exemple canonique de la les continuations de Scala, délimités légèrement modifiés afin de l'entrée de fonction shift est donnée le nom f et est donc plus anonyme.

def f(k: Int => Int): Int = k(k(k(7)))
reset(
  shift(f) + 1   // replace from here down with `f(k)` and move to `k`
) * 2

Le module Scala transforme cet exemple de telle sorte que le calcul (dans le paramètre d'entrée de reset) à partir de chaque shift à l'invocation de reset est remplacé avec l'entrée de fonction (par exemple f) à shift.

Le calcul est remplacé décalée (à savoir déplacé) dans un k de fonction. La fonction f entrées de la fonction k, où k contient le calcul remplacé, entrées k x: Int, et le calcul en k remplace shift(f) avec x.

f(k) * 2
def k(x: Int): Int = x + 1

Ce qui a le même effet que:

k(k(k(7))) * 2
def k(x: Int): Int = x + 1

Notez le type Int du paramètre d'entrée x (à savoir la signature du type de k) a été donné par la signature de type du paramètre d'entrée de f.

Une autre emprunté exemple avec l'abstraction conceptuelle équivalente, à savoir read est l'entrée de la fonction de shift:

def read(callback: Byte => Unit): Unit = myCallback = callback
reset {
  val byte = "byte"

  val byte1 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "1 = " + byte1)
  val byte2 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "2 = " + byte2)
}

Je crois que ce serait traduit à l'équivalent logique:

val byte = "byte"

read(callback)
def callback(x: Byte): Unit {
  val byte1 = x
  println(byte + "1 = " + byte1)
  read(callback2)
  def callback2(x: Byte): Unit {
    val byte2 = x
    println(byte + "2 = " + byte1)
  }
}

J'espère que cette élucide l'abstraction commune cohérente qui a été quelque peu occulté par la présentation préalable de ces deux exemples. Par exemple, le premier exemple canonique a été présenté dans le comme une fonction anonyme, au lieu de mon nom f, donc il n'a pas été immédiatement clair pour certains lecteurs qu'il était abstraitement analogue à la read dans la emprunté deuxième exemple.

continuations Ainsi créent l'illusion délimité d'une inversion de contrôle de « vous me appelez de l'extérieur de reset » à « Je vous appelle à l'intérieur reset ».

Notez le type de retour de f est, mais k n'est pas, nécessaire pour être le même que le type de retour de reset, à savoir f a la liberté de déclarer tout type de retour pour k aussi longtemps que f retourne le même type que reset . Même chose pour read et capture (voir aussi ENV ci-dessous).


Délimité continuations ne pas inverser implicitement le contrôle de l'Etat, par exemple read et callback ne sont pas pures fonctions. Ainsi le contrôle l'appelant ne peut pas créer des expressions ont référentielle transparentes et ne donc pas déclarative (aka transparent) sur l'intention impératif sémantique .

On peut obtenir explicitement des fonctions pures avec continuations délimitées.

def aread(env: ENV): Tuple2[Byte,ENV] {
  def read(callback: Tuple2[Byte,ENV] => ENV): ENV = env.myCallback(callback)
  shift(read)
}
def pure(val env: ENV): ENV {
  reset {
    val (byte1, env) = aread(env)
    val env = env.println("byte1 = " + byte1)
    val (byte2, env) = aread(env)
    val env = env.println("byte2 = " + byte2)
  }
}

Je crois que ce serait traduit à l'équivalent logique:

def read(callback: Tuple2[Byte,ENV] => ENV, env: ENV): ENV =
  env.myCallback(callback)
def pure(val env: ENV): ENV {
  read(callback,env)
  def callback(x: Tuple2[Byte,ENV]): ENV {
    val (byte1, env) = x
    val env = env.println("byte1 = " + byte1)
    read(callback2,env)
    def callback2(x: Tuple2[Byte,ENV]): ENV {
      val (byte2, env) = x
      val env = env.println("byte2 = " + byte2)
    }
  }
}

Cela devient bruyant, en raison de l'environnement explicite.

tangentiellement note, Scala n'a pas l'inférence de type global de Haskell et donc autant que je sache, ne pouvait pas supporter de levage implicite à la unit d'une monade de l'État (comme une stratégie possible pour cacher l'environnement explicite), parce que mondial (Hindley- Haskell Milner) inférence de type dépend de ne supportant pas l'héritage multiple diamant virtuel .

Suite capture l'état d'un calcul, à invoquer plus tard.

Pensez au calcul entre la fin de l'expression de changement de vitesse et en laissant l'expression remise à zéro en fonction. A l'intérieur de l'expression de changement cette fonction est appelée k, il est la continuation. Vous pouvez passer autour, invoquer plus tard, plus d'une fois.

Je pense que la valeur retournée par l'expression de remise à zéro est la valeur de l'expression dans l'expression de changement après la =>, mais sur ce que je ne suis pas tout à fait sûr.

Donc, avec continuations, vous pouvez envelopper un morceau plutôt arbitraire et non local de code dans une fonction. Cela peut être utilisé pour mettre en œuvre le flux de contrôle non standard, tels que coroutining ou retours en arrière.

continuations doivent être utilisés au niveau du système. les aspergeant par votre code d'application serait une recette sûre pour des cauchemars, bien pire que le pire code spaghetti en utilisant goto pourrait jamais.

Disclaimer:. Je n'ai aucune connaissance approfondie des continuations à Scala, je viens de déduire qu'il en regardant les exemples et sachant continuations du schéma

De mon point de vue, a été donné la meilleure explication ici: http : //jim-mcbeath.blogspot.ru/2010/08/delimited-continuations.html

L'un des exemples:

  

Pour voir le contrôle de flux un peu plus clair, vous pouvez exécuter cette   extrait de code:

reset {
    println("A")
    shift { k1: (Unit=>Unit) =>
        println("B")
        k1()
        println("C")
    }
    println("D")
    shift { k2: (Unit=>Unit) =>
        println("E")
        k2()
        println("F")
    }
    println("G")
}
  

Voici la sortie du code ci-dessus produit:

A
B
D
E
G
F
C

Une autre (plus récente - Mai 2016) article sur continuations Scala est:
« Voyage dans le temps Scala: CPS à Scala (la continuation de scala) » par Shivansh Srivastava (shiv4nsh) .
Il se réfère également à Jim McBeath article mentionné dans Dmitry Bespalov 's réponse .

Mais avant cela, il décrit continuations comme ceci:

  

Une suite est une représentation abstraite de l'état de contrôle d'un programme informatique .
  Donc, ce que cela signifie en fait est qu'il est une structure de données qui représente le processus de calcul à un moment donné dans l'exécution du processus; la structure de données créée est accessible par le langage de programmation, au lieu d'être caché dans l'environnement d'exécution.

     

Pour expliquer plus loin, nous pouvons avoir l'un des exemples les plus classiques,

     

Dites que vous êtes dans la cuisine devant le réfrigérateur, en pensant à un sandwich. Vous prenez une continuation là et le coller dans votre poche.
  Ensuite, vous obtenez la dinde et du pain hors du réfrigérateur et de vous faire un sandwich, qui est maintenant assis sur le comptoir.
  Vous invoquez la continuation dans votre poche, et vous vous retrouvez debout devant le réfrigérateur à nouveau, en pensant à un sandwich. Mais heureusement, il y a un sandwich sur le comptoir, et tous les matériaux utilisés pour le faire sont partis. Donc, vous mangez. : -)

     

Dans cette description, la sandwich fait partie des données du programme (par exemple, un objet sur le tas), et plutôt que d'appeler une routine « make sandwich » puis retour, la personne appelée « make sandwich with current continuation » routine, ce qui crée le sandwich et se poursuit alors où l'exécution a laissé.

Cela étant dit, comme annoncé dans Avril 2014 pour Scala 2.11.0-RC1

  

Nous recherchons des mainteneurs de prendre en charge les modules suivants: scala-swing , < a href = "https://github.com/scala/scala-continuations" rel = "nofollow noreferrer"> scala-continuations .
   2.12 ne sera pas les inclure si aucun nouveau mainteneur se trouve .
  Nous verrons probablement continuer à maintenir les autres modules (scala-xml, scala-analyseur-combinators), mais l'aide est toujours très apprécié.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top