2009-10-03 5 views
79

Я только что закончил Programming in Scala, и я изучал изменения между Scala 2.7 и 2.8. Тот, который кажется самым важным, является плагином продолжения, но я не понимаю, для чего он полезен или как он работает. Я видел, что это хорошо для асинхронного ввода-вывода, но я не смог выяснить, почему. Некоторые из наиболее популярных ресурсов на эту тему являются следующие:Что такое продолжения Скалы и зачем их использовать?

И этот вопрос на Stack   Переполнение:

К сожалению, ни одна из этих ссылок не пытаются определить, что продолжениями являются для или то, что сдвиг/сброс функции должны делать, и Я не нашел никаких ссылок. Я не мог догадаться, как работает какой-либо из примеров в связанных статьях (или что они делают), поэтому одним из способов помочь мне может стать переход по одному из этих образцов. Даже этот простой вариант из третьей статьи:

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

Почему результат 8? Возможно, это поможет мне начать.

+0

http://www.scala-lang.org/api/current/index.html#scala.util.continuations.package – Vadzim

ответ

35

Мой blog действительно объясняет, что reset и shift сделать, чтобы вы могли снова прочитать это.

Другим хорошим источником, который я также указываю в своем блоге, является запись в Википедии по адресу continuation passing style. Этот, безусловно, самый ясный в этом вопросе, хотя он не использует синтаксис Scala, и продолжение явно передается.

Документ о разграниченных продолжениях, на который я ссылаюсь в своем блоге, но, похоже, сломался, дает много примеров использования.

Но я думаю, что лучший пример концепции разграниченных продолжений - это Scala Swarm. В нем библиотека останавливает выполнение вашего кода в одной точке, а оставшееся вычисление становится продолжением. Затем библиотека делает что-то - в этом случае переносит вычисление на другой хост и возвращает результат (значение переменной, к которой был обращен доступ) к остановленному вычислению.

Теперь вы не понимаете даже простой пример на странице Scala, поэтому do прочитал мой блог. В нем я всего лишь , связанный с объяснением этих основ, почему результат 8.

+0

Я перечитал вашу запись в блоге и на этот раз я застрял с ним - я Думаю, у меня есть лучшее представление о том, что происходит. Я не получил многого со страницы Википедии (я уже знал продолжения Лиспа), но стиль отложенного/сдвинутого отложенного режима или все, что его вызывало, заставил меня замолчать. Для нетерпеливого (то есть самого себя) ваше описание было в порядке, но люди должны обязательно придерживаться его до «Результат сброса - результат кода внутри смены». пункт ... Я был безнадежно потерян до этого момента, но он становится яснее! Я посмотрю на Рой, потому что мне все еще интересно, для чего это. Спасибо! – Dave

+0

Да, это займет время, пока все не начнет иметь смысл. Я не чувствовал, что могу ускользнуть от объяснения. –

+0

Все это собралось для меня, когда я пришел к осознанию того, что «сброс ограничивает объем продолжения» (т. Е. Переменные и утверждения, которые должны быть включены). – JeffV

8

Продолжение захвата состояния вычисления, которое будет вызываться позже.

Подумайте о вычислении между выводом выражения сдвига и оставлением выражения сброса как функции. Внутри выражения сдвига эта функция называется k, это продолжение. Вы можете передать его, вызывать его позже, даже более одного раза.

Я думаю, что значение, возвращаемое выражением сброса, является значением выражения внутри выражения shift после =>, но об этом я не совсем уверен.

Таким образом, с продолжением вы можете завернуть в произвольную и нелокальную часть кода функцию. Это может быть использовано для реализации нестандартного потока управления, такого как обработка или обратная трассировка.

Поэтому продолжения следует использовать на системном уровне. Посыпать их через ваш код приложения будет верным рецептом кошмаров, намного хуже, чем самый худший код спагетти с использованием goto.

Отказ от ответственности: У меня нет глубокого понимания продолжений в Scala, я просто сделал вывод о том, что он смотрит на примеры и знает продолжения из Схемы.

31

Я нашел, что существующие объяснения менее эффективны при объяснении концепции, чем я надеюсь. Я надеюсь, что это ясно (и правильно.) Я еще не использовал продолжения.

Когда функция продолжения cf называется:

  1. Выполнение скачет над остальной частью shift блока и снова начинается в конце этого
    • параметр передается cf является то, что shift блок «оценивает», поскольку выполнение продолжается. это может быть различным для каждого вызова cf
  2. выполнение продолжается до конца reset блока (или до вызова reset, если нет блока)
    • в результате reset блока (или параметр для reset(), если нет блока) является то, что не cf возвращается
  3. Выполнение продолжается после cf до конца shift блока
  4. Выполнение скачет до конца reset блока (или вызова для сброса?)

Таким образом, в этом примере, следуют буквы от А до 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 

Это печатает:

11 
101 
+2

У меня ошибка: «не могу вычислить тип для результата функции преобразования CPS», когда я попытался скомпилировать его. Я не уверен, что это не так, как исправить его. –

+0

@Fabio Veronez Добавить оператор возврата в конец сдвига: измените 'println (oneHundredOne)}', скажем, 'println (oneHundredOne); oneHundredOne} '. – folone

+0

Хорошее объяснение для ужасного синтаксиса. Объявление функции продолжения странно отделено от ее тела. Я бы не хотел делиться этим кодом с другими людьми. – joeytwiddle

8

Учитывая канонический пример из research paper для разграниченных продолжений Scala, слегка измененный, поэтому задана функция, вводимая в shift имя f и, следовательно, уже не является анонимным.

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 

Скала плагин преобразует этот пример таким образом, что вычисление (в пределах входного аргумента reset), начиная с каждого shift с вызовом reset является заменен с функцией (например f) ввода в shift.

Замененное вычисление смещено (т. Е. Перемещено) в функцию k. Функция f вводит функцию k, где kсодержит замененного вычисление, k входов x: Int и вычисление в k заменяют shift(f) с x.

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

который имеет тот же эффект, как:

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

Обратите внимание на тип Int входного параметра x (т.е. тип подписи k) был дан тип подписи входного параметра f ,

Другой borrowed пример с концептуально эквивалентной абстракции, т.е. read функция вход 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) 
} 

Я считаю, что это будет переведено на логический эквивалент:

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) 
    } 
} 

Я надеюсь, это объясняет когерентную общую абстракцию, которая была несколько запутана предшествующим представлением этих двух примеров. Например, канонический первый пример был представлен в research paper как анонимная функция, вместо моего имени f, поэтому некоторые читатели не сразу поняли, что он был абстракционно подобен второму примеру read в borrowed.

Таким образом, разграниченные продолжения создают иллюзию инверсии управления из «вы называете меня из-за пределов reset» на «Я звоню вам внутри reset».

Примечания возвращаемого типа f есть, но k нет, требуется, чтобы быть таким же, как тип возвращаемого reset, т.е. f имеет право на свободу, чтобы объявить любой тип возвращаемых данных для k, пока f возвращает тот же типа, reset. То же для read и capture (см. Также ENV ниже).


Ограниченные продолжения не неявно инвертируют управление состоянием, например. read и callback не являются чистыми функциями. Таким образом, вызывающий объект не может создавать ссылочно прозрачные выражения и, следовательно, не имеет declarative (a.k.a. transparent) control over intended imperative semantics.

Мы можем явно получить чистые функции с разграниченными продолжениями.

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) 
    } 
} 

Я считаю, что это будет переведено на логический эквивалент:

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) 
    } 
    } 
} 

Это становится шумным, из-за явной среды.

Заметьте, что у Scala нет глобального типа вывода Haskell, и, насколько мне известно, не может поддерживать неявный подъем до состояния монады unit (как одна из возможных стратегий скрытия явной среды), потому что глобальный Haskell (Hindley -Milner), зависит от not supporting diamond multiple virtual inheritance.

+0

[Я предлагаю] (https://groups.google.com/d/msg/scala-debate/MXQ52tlRK6E/KniOsRa-9sAJ), который 'reset' /' shift' будет изменен на 'delimit' /' replace'. И по соглашению, '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' обратный вызов' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' –

+0

с ключевым словом. P.S. Некоторые из ваших сбрасываний имеют(), которые должны быть {} В любом случае отличная запись! – nafg

+0

@nafg спасибо, поэтому я буду предлагать 'замену' вместо' with'. Afaik, '()' также разрешено? Afaik, '{}' является [«легкий синтаксис Scala для закрытия»] (http://blog.bruchez.name/2011/09/continuations-in-scala.html), который скрывает вызов базовой функции.Например, посмотрите, как [я переписал «последовательность» Дэниела] (http://goldwetrust.up-with.com/t112p135-computers#4242) (обратите внимание, что код никогда не компилировался и не тестировался, поэтому, пожалуйста, не стесняйтесь исправить меня) , –

3

С моей точки зрения, самое лучшее объяснение было дано здесь: http://jim-mcbeath.blogspot.ru/2010/08/delimited-continuations.html

Один из примеров:

Чтобы увидеть управление потоком немного более ясно, вы можете выполнить этот фрагмент кода:

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") 
} 

Вот вывод АВ Ов код производит:

A 
B 
D 
E 
G 
F 
C 
1

Другой (более поздней - май 2016) статья о Scala продолжениях является:
"Time Travel in Scala: CPS in Scala (scala’s continuation)" по Shivansh Srivastava (shiv4nsh).
Он также относится к Jim McBeatharticle, указанному в Dmitry Bespalov's answer.

Но до этого, он описывает Продолжения так:

Продолжение абстрактное представление состояния управления компьютерной программы.
Так что это на самом деле означает, что это структура данных, которая представляет вычислительный процесс в данной точке процесса выполнения; к созданной структуре данных может быть доступен язык программирования, а не скрыт в среде выполнения.

Чтобы объяснить его дальше, мы можем иметь один из самых классических примера,

сказать, что вы на кухне перед холодильником, думая о бутерброде. Вы берете продолжение прямо там и вставляете его в карман.
Затем вы получите из турбины индейку и хлеб и сделайте себе бутерброд, который теперь сидит на прилавке.
Вы вызываете продолжение в кармане, и вы снова оказываетесь перед холодильником, думая о бутерброде. Но, к счастью, на прилавке есть бутерброд, и все материалы, используемые для его изготовления, исчезли. Таким образом, вы едите его. :-)

В этом описании sandwich является частью данных программы (например,, объект в куче), а вместо вызова процедуры «make sandwich», а затем возвращается, человек, называемый «make sandwich with current continuation», который создает сэндвич, а затем продолжается, когда выполнение прекращено.

Это, как говорится, как было объявлено в April 2014 for Scala 2.11.0-RC1

Мы ищем сопровождающими, чтобы взять на себя следующие модули: scala-swing, scala-continuations.
2.12 не будет включать их, если новый сопровождающий не найден.
Мы, скорее всего, будем поддерживать другие модули (scala-xml, scala-parser-combinators), но помощь по-прежнему очень ценится.

Смежные вопросы