Что такое продолжения Scala и зачем их использовать?

Я только что закончил Программирование в Scala , и я Я изучал изменения между Scala 2.7 и 2.8. Наиболее важным является плагин продолжения, но я не понимаю, для чего он полезен или как он работает. Я видел, что это хорошо для асинхронного ввода-вывода, но я не смог выяснить, почему. Вот некоторые из наиболее популярных ресурсов по этому вопросу:

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

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

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

Почему результат 8? Это, вероятно, поможет мне начать.

84 голоса | спросил Dave 3 +04002009-10-03T09:54:37+04:00312009bEurope/MoscowSat, 03 Oct 2009 09:54:37 +0400 2009, 09:54:37

6 ответов


0

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

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

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

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

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

ответил Daniel C. Sobral 3 +04002009-10-03T19:04:35+04:00312009bEurope/MoscowSat, 03 Oct 2009 19:04:35 +0400 2009, 19:04:35
0

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

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

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

Так что в этом примере следуйте буквам от А до Я

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
ответил Alex Neth 5 42009vEurope/Moscow11bEurope/MoscowThu, 05 Nov 2009 15:02:23 +0300 2009, 15:02:23
0

Учитывая канонический пример из исследовательской работы для Продолжения в 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

Плагин Scala преобразует этот пример так, что вычисления (в пределах входного аргумента 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

Еще один заимствованный пример с концептуально эквивалентной абстракцией, т.е. 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)
  }
}

Я надеюсь, что это объясняет общую связную абстракцию, которая была несколько омрачена предварительным представлением этих двух примеров. Например, первый канонический пример был представлен в исследовательской работе как анонимная функция вместо моего именованного f, поэтому некоторые читатели не сразу поняли, что это абстрактно аналогично ---- +: = 28 =: + ---- в заимствовано второй пример.

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

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


Продолжения с разделителями не неявно инвертируют контроль состояния, например, ENV и read не являются чистыми функциями. Таким образом, вызывающая сторона не может создавать ссылочно-прозрачные выражения и, следовательно, не имеет декларативного (он же прозрачного) контроля над намеченным императивом семантика .

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

callback

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

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

Это становится шумно из-за явного окружения.

Заметим, что у Scala нет глобального вывода типов из Haskell, и поэтому, насколько мне известно, не может поддерживать неявное поднятие в 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) } } }

ответил Shelby Moore III 5 12012vEurope/Moscow11bEurope/MoscowMon, 05 Nov 2012 20:21:36 +0400 2012, 20:21:36
0

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

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

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

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

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

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

ответил starblue 3 +04002009-10-03T10:57:46+04:00312009bEurope/MoscowSat, 03 Oct 2009 10:57:46 +0400 2009, 10:57:46
0

С моей точки зрения, лучшее объяснение было дано здесь: 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
ответил Dmitry Bespalov 19 PMpTue, 19 Apr 2016 19:59:33 +030059Tuesday 2016, 19:59:33
0

Еще одна (более поздняя - май 2016 г.) статья о продолжении Scala:
Путешествие во времени в Scala: CPS в Scala (продолжение scala) " Шиванш Шривастава (shiv4nsh) .
Это также относится к Джиму МакБиту статья , упомянутая в Дмитрий Беспалов ответ .

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

  

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

     

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

     

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

     

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

При этом, как было объявлено в Апрель 2014 года для Scala 2.11.0-RC1

  

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

ответил VonC 1 PM00000090000003531 2016, 21:20:35

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132