स्काला निरंतरता क्या हैं और उनका उपयोग क्यों करते हैं?


85

मैंने अभी स्काला में प्रोग्रामिंग समाप्त कर दिया है , और मैं स्केल 2.7 और 2.8 के बीच के बदलावों को देख रहा हूं। जो सबसे महत्वपूर्ण लगता है वह है निरंतरता प्लगइन, लेकिन मुझे समझ नहीं आता कि यह किस लिए उपयोगी है या यह कैसे काम करता है। मैंने देखा है कि यह अतुल्यकालिक I / O के लिए अच्छा है, लेकिन मुझे यह पता नहीं चल पाया है कि क्यों। इस विषय पर कुछ अधिक लोकप्रिय संसाधन निम्नलिखित हैं:

और स्टैक ओवरफ्लो पर यह सवाल:

दुर्भाग्य से, इनमें से कोई भी संदर्भ यह परिभाषित करने की कोशिश नहीं करता है कि निरंतरता क्या है या शिफ्ट / रीसेट फ़ंक्शंस क्या करने वाले हैं, और मुझे ऐसा कोई संदर्भ नहीं मिला है। मैं यह अनुमान नहीं लगा पाया हूं कि लिंक किए गए लेखों में कोई भी उदाहरण कैसे काम करता है (या वे क्या करते हैं), इसलिए मेरी मदद करने का एक तरीका उन नमूनों में से एक के माध्यम से लाइन-बाय-लाइन जाना हो सकता है। यहां तक ​​कि तीसरे लेख से यह सरल है:

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

परिणाम 8 क्यों है? इससे शायद मुझे शुरुआत करने में मदद मिलेगी।


जवाबों:


38

मेरा ब्लॉग समझाता है कि क्या resetऔर क्या shiftकरना है, इसलिए आप फिर से पढ़ना चाहते हैं।

एक और अच्छा स्रोत, जिसे मैं अपने ब्लॉग में इंगित करता हूं, निरंतरता गुजर शैली पर विकिपीडिया प्रविष्टि है । यह एक है, अब तक, इस विषय पर सबसे स्पष्ट है, हालांकि यह स्काला सिंटैक्स का उपयोग नहीं करता है, और निरंतरता स्पष्ट रूप से पारित हो जाती है।

सीमांकित निरंतरता पर कागज, जिसे मैं अपने ब्लॉग में लिंक करता हूं, लेकिन लगता है कि यह टूट गया है, उपयोग के कई उदाहरण देता है।

लेकिन मुझे लगता है कि सीमांकित निरंतरता की अवधारणा का सबसे अच्छा उदाहरण स्काला झुंड है। इसमें, लाइब्रेरी एक बिंदु पर आपके कोड के निष्पादन को रोक देती है, और शेष गणना निरंतरता बन जाती है। पुस्तकालय तब कुछ करता है - इस मामले में, गणना को दूसरे होस्ट में स्थानांतरित करता है, और परिणाम को रोक दिए गए गणना के लिए परिणाम (चर का मान जो एक्सेस किया गया था) लौटाता है।

अब, आप स्काला पृष्ठ पर भी सरल उदाहरण समझ में नहीं आता है, तो है मेरे ब्लॉग पढ़ें। इसमें मुझे केवल इन मूल बातों की व्याख्या करने की चिंता है कि परिणाम क्यों है 8


मैंने आपके ब्लॉग प्रविष्टि को फिर से पढ़ा और इस बार मैं इसके साथ अटक गया - मुझे लगता है कि मेरे पास बेहतर विचार है कि क्या चल रहा है। मुझे विकिपीडिया पृष्ठ से बहुत कुछ नहीं मिला (मैं पहले से ही लिस्प निरंतरता जानता हूं) लेकिन रीसेट / शिफ्ट आस्थगित शैली या इसके जो कुछ भी मुझे बुलाया गया था वह स्टम्प्ड था। अधीर (यानी खुद के लिए) आपका विवरण ठीक था लेकिन लोगों को इसके साथ चिपकना सुनिश्चित करना होगा "रीसेट का परिणाम शिफ्ट के अंदर कोड का परिणाम है।" पैराग्राफ ... मैं निराशाजनक रूप से उस बिंदु तक खो गया था, लेकिन यह स्पष्ट हो जाता है! मैं झुंड को देखूंगा क्योंकि मैं अभी भी उत्सुक हूं कि यह किस लिए है। धन्यवाद!
डेव

हां, इसमें समय लगता है जब तक कि चीजें समझ में न आने लगें। मुझे नहीं लगा कि मैं स्पष्टीकरण को और तेज कर सकता हूं।
डैनियल सी। सोबरल

यह सब मेरे लिए एक साथ आया था जब मुझे एहसास हुआ कि "रीसेट निरंतरता के दायरे का परिसीमन करता है। (यानी: चर और बयान शामिल किए जाने के लिए।)
जेएफवी

1
आपकी व्याख्या मौखिक थी और समझ के सार तक नहीं थी। उदाहरण लंबे थे, मुझे पहले पैराग्राफ में इतनी समझ नहीं थी कि मुझे यह सब पढ़ने के लिए प्रेरित किया जाए। इसलिए मैंने इसे वोट दिया। SO मुझे वोट करने के बाद एक संदेश प्रदर्शित करता है, मुझसे टिप्पणी जोड़ने के लिए कहता है, इसलिए मैं इसका अनुपालन कर रहा हूं। मेरी फ्रेंकनेस के लिए माफ़ी।
शेल्बी मूर III

1
मैंने इस बारे में नियंत्रण प्रवाह (कार्यान्वयन के विवरण पर चर्चा किए बिना) पर ध्यान देने के साथ ब्लॉग किया है। wherenullpoints.com/2014/04/scala-continuations.html
अलेक्जेंड्रोस

31

मुझे लगता है कि मुझे उम्मीद है कि अवधारणा को समझाने के लिए मौजूदा स्पष्टीकरण कम प्रभावी होंगे। मुझे आशा है कि यह स्पष्ट है (और सही है।) मैंने अभी तक निरंतरता का उपयोग नहीं किया है।

जब एक निरंतरता समारोह 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

2
मुझे यह कहते हुए एक त्रुटि हुई है कि "जब मैं इसे संकलित करने की कोशिश कर रहा हूं तो
CPS-

@ फ़ेबियो वेरोनज़ शिफ्ट के अंत में एक रिटर्न स्टेटमेंट जोड़ें: इसमें बदलाव println(oneHundredOne) }करें, कहें println(oneHundredOne); oneHundredOne },।
फोलोन

एक भयानक वाक्यविन्यास के लिए अच्छी व्याख्या। निरंतरता समारोह की घोषणा अजीब तरह से अपने शरीर से अलग है। मैं इस तरह के हेड-स्क्रैचिंग कोड को दूसरों के साथ साझा करने के लिए अनिच्छुक रहूंगा।
joeytwiddle

cannot compute type for CPS-transformed function resultत्रुटि से बचने के लिए , +1तुरंत बाद का पालन करेंगे oneHundredOne}। वर्तमान में उनके बीच रहने वाली टिप्पणियाँ किसी तरह व्याकरण को तोड़ती हैं।
lcn

9

स्काला के सीमांकित निरंतरताओं के लिए शोध पत्र से विहित उदाहरण को देखते हुए , थोड़ा संशोधित किया गया है ताकि फ़ंक्शन इनपुट को 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

वैचारिक समकक्ष अमूर्तता के साथ एक और उधार लिया गया उदाहरण है, यानी 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, इस प्रकार यह तुरंत कुछ पाठकों के लिए स्पष्ट नहीं था कि उधार के दूसरे उदाहरण readमें यह सार के अनुरूप था ।

इस प्रकार सीमांकित निरंतरता एक उलटा नियंत्रण का भ्रम पैदा करती है "आप मुझे बाहर से बुलाते हैं reset" से "मैं आपको कॉल करता हूं reset"।

वापसी प्रकार पर ध्यान दें f, लेकिन, वापसी के प्रकार के kसमान होने की आवश्यकता नहीं है reset, अर्थात fकिसी भी प्रकार के रिटर्न को उसी प्रकार घोषित करने की स्वतंत्रता है kजब तक fकि उसी प्रकार का रिटर्न नहीं होता है reset। Ditto के लिए 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)
  }
}

मेरा मानना ​​है कि यह तार्किक समकक्ष के लिए अनुवादित होगा:

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

यह स्पष्ट वातावरण के कारण, शोर हो रहा है।

तात्कालिक रूप से, स्काला के पास हास्केल के वैश्विक प्रकार के अनुमान नहीं हैं और इस प्रकार जहां तक ​​मुझे पता है कि एक राज्य मोनड्स को स्पष्ट उठाने का समर्थन नहीं कर सकता है unit(स्पष्ट वातावरण को छिपाने के लिए एक संभावित रणनीति के रूप में), क्योंकि हास्केल का वैश्विक (हिंदले-मिलनर) प्रकार की संसेचन। हीरे के कई आभासी विरासत का समर्थन नहीं करने पर निर्भर करता है ।


मैं प्रस्ताव कर रहा हूँ कि reset/ shiftके लिए बदला जा delimit/ replace। और प्रथा के अनुसार, कि fऔर readहो with, और kऔर callbackहो replaced, captured, continuation, या callback
शेल्बी मूर तृतीय

एक खोजशब्द है। PS आपके कुछ रीसेट में () होना चाहिए {} वैसे भी बढ़िया राइटअप!
nafg

@nafg धन्यवाद, इसलिए मैं replacementइसके बजाय प्रस्ताव रखूंगा with। अफाक ()को भी अनुमति है? AFAIK, {}है "बंद के लिए स्काला के हल्के वाक्य रचना" है, जो एक अंतर्निहित समारोह कॉल छिपा है। उदाहरण के लिए, देखें कि मैंनेsequence कैसे डैनियल को फिर से लिखा (ध्यान दें कि कोड कभी भी संकलित या परीक्षण नहीं किया गया था, इसलिए कृपया मुझे सही करने के लिए स्वतंत्र महसूस करें)।
शेल्बी मूर III

1
एक ब्लॉक - अर्थात, एक अभिव्यक्ति जिसमें कई कथन हैं - को घुंघराले ब्रेस की आवश्यकता होती है।
nafg

@nafg, सही है। Afaik shift resetलाइब्रेरी फ़ंक्शंस हैं, न कि कीवर्ड्स। इस प्रकार {}या ()उपयोग किया जा सकता है जब फ़ंक्शन केवल एक पैरामीटर की अपेक्षा करता है । स्काला में बाय-नेम पैरामीटर हैं (स्कैला में प्रोग्रामिंग का खंड "9.5 नियंत्रण अंश" देखें, दूसरा संस्करण। पृष्ठ 218), जहां यदि पैरामीटर टाइप का है () => ...तो () =>इसे समाप्त किया जा सकता है। मैं मान लेता हूं Unitऔर नाम से नहीं, क्योंकि ब्लॉक को resetलागू करने से पहले मूल्यांकन करना चाहिए , लेकिन मुझे {}कई बयानों की आवश्यकता है । मेरा उपयोग shiftसही है, क्योंकि यह स्पष्ट रूप से एक फ़ंक्शन प्रकार का इनपुट करता है।
शेल्बी मूर III

8

निरंतरता एक संगणना की स्थिति पर कब्जा कर लेती है, जिसे बाद में आमंत्रित किया जाता है।

शिफ्ट एक्सप्रेशन छोड़ने और फ़ंक्शन के रूप में रीसेट एक्सप्रेशन छोड़ने के बीच गणना के बारे में सोचें। शिफ्ट एक्सप्रेशन के अंदर इस फंक्शन को k कहा जाता है, यह निरंतरता है। आप इसे चारों ओर से गुजर सकते हैं, बाद में इसे लागू कर सकते हैं, यहां तक ​​कि एक से अधिक बार भी।

मुझे लगता है कि रीसेट अभिव्यक्ति द्वारा लौटाया गया मूल्य => के बाद पारी अभिव्यक्ति के अंदर की अभिव्यक्ति का मूल्य है, लेकिन इसके बारे में मुझे यकीन नहीं है।

तो निरंतरता के साथ आप किसी फ़ंक्शन में कोड के बजाय मनमाने और गैर-स्थानीय टुकड़े को लपेट सकते हैं। इसका उपयोग गैर-मानक नियंत्रण प्रवाह को लागू करने के लिए किया जा सकता है, जैसे कि coroutining या backtracking।

तो सिस्टम स्तर पर निरंतरता का उपयोग किया जाना चाहिए। अपने आवेदन कोड के माध्यम से उन्हें छिड़कना बुरे सपने के लिए एक निश्चित नुस्खा होगा, गोटो का उपयोग करने वाले सबसे खराब स्पेगेटी कोड की तुलना में बहुत खराब हो सकता है।

डिस्क्लेमर: मुझे स्काला में निरंतरता की गहराई से समझ नहीं है, मैंने इसे केवल उदाहरणों को देखने और स्कीम से निरंतरताओं को जानने के लिए अनुमान लगाया है।


5

मेरे दृष्टिकोण से, सबसे अच्छा स्पष्टीकरण यहाँ दिया गया था: 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) स्काला निरंतरता पर लेख है:
" शिवांश श्रीवास्तव ( ) द्वारा स्काला में सीपीएस: स्काला में सीपीएस (स्काला की निरंतरता) " । यह दिमित्री बेस्पालोव के उत्तर में उल्लिखित जिम मैकबेथ के लेख को भी संदर्भित करता है ।shiv4nsh

लेकिन इससे पहले, यह कंटिन्यू का वर्णन करता है जैसे:

एक निरंतरता एक कंप्यूटर प्रोग्राम के नियंत्रण राज्य का एक सार प्रतिनिधित्व है
तो वास्तव में इसका मतलब यह है कि यह एक डेटा संरचना है जो प्रक्रिया के निष्पादन में दिए गए बिंदु पर कम्प्यूटेशनल प्रक्रिया का प्रतिनिधित्व करता है; रनटाइम वातावरण में छिपे होने के बजाय, बनाई गई डेटा संरचना को प्रोग्रामिंग भाषा द्वारा एक्सेस किया जा सकता है।

इसे आगे समझाने के लिए हम सबसे क्लासिक उदाहरणों में से एक हो सकते हैं,

कहते हैं कि आप रेफ्रिजरेटर के सामने रसोई में हैं, एक सैंडविच के बारे में सोच रहे हैं। आप एक निरंतरता लेते हैं और इसे अपनी जेब में रखते हैं।
फिर आप रेफ्रिजरेटर से कुछ टर्की और ब्रेड प्राप्त करते हैं और अपने आप को एक सैंडविच बनाते हैं, जो अब काउंटर पर बैठा है।
आप अपनी जेब में निरंतरता का आह्वान करते हैं, और आप अपने आप को एक रेफ्रिजरेटर के बारे में सोचते हुए, फिर से रेफ्रिजरेटर के सामने खड़े पाते हैं। लेकिन सौभाग्य से, काउंटर पर एक सैंडविच है, और इसे बनाने के लिए उपयोग की जाने वाली सभी सामग्रियां चली गई हैं। इसलिए आप इसे खाएं। :-)

इस विवरण में, प्रोग्राम डेटाsandwich का हिस्सा है (उदाहरण के लिए, ढेर पर एक वस्तु), और एक " " रूटीन को कॉल करने के बजाय और फिर वापस लौटना, उस व्यक्ति को "रूटीन " कहा जाता है , जो सैंडविच बनाता है और फिर निष्पादन को जारी रखता है। दूर छोड़ दिया।make sandwichmake sandwich with current continuation

जैसा कि कहा जा रहा है, जैसा कि स्कैल 2.11.0-RC1 के लिए अप्रैल 2014 में घोषित किया गया था

हम निम्नलिखित मॉड्यूल को संभालने के लिए रखवाले की तलाश कर रहे हैं: स्केला-स्विंग , स्केला-निरंतरता
यदि कोई नया अनुरक्षक नहीं मिला तो 2.12 उन्हें शामिल नहीं करेगा
हम संभवतः अन्य मॉड्यूल (scala-xml, scala-parser-combinators) को बनाए रखेंगे, लेकिन मदद अभी भी बहुत सराहना की जा रही है।


0

सार्थक उदाहरणों के माध्यम से स्काला निरंतरता

हमें परिभाषित करते हैं from0to10कि 0 से 10 तक पुनरावृति के विचार को व्यक्त करता है:

def from0to10() = shift { (cont: Int => Unit) =>
   for ( i <- 0 to 10 ) {
     cont(i)
   }
}

अभी,

reset {
  val x = from0to10()
  print(s"$x ")
}
println()

प्रिंट:

0 1 2 3 4 5 6 7 8 9 10 

वास्तव में, हमें इसकी आवश्यकता नहीं है x:

reset {
  print(s"${from0to10()} ")
}
println()

एक ही परिणाम प्रिंट करता है।

तथा

reset {
  print(s"(${from0to10()},${from0to10()}) ")
}
println()

सभी जोड़े प्रिंट:

(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,8) (0,9) (0,10) (1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7) (1,8) (1,9) (1,10) (2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7) (2,8) (2,9) (2,10) (3,0) (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7) (3,8) (3,9) (3,10) (4,0) (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7) (4,8) (4,9) (4,10) (5,0) (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7) (5,8) (5,9) (5,10) (6,0) (6,1) (6,2) (6,3) (6,4) (6,5) (6,6) (6,7) (6,8) (6,9) (6,10) (7,0) (7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7) (7,8) (7,9) (7,10) (8,0) (8,1) (8,2) (8,3) (8,4) (8,5) (8,6) (8,7) (8,8) (8,9) (8,10) (9,0) (9,1) (9,2) (9,3) (9,4) (9,5) (9,6) (9,7) (9,8) (9,9) (9,10) (10,0) (10,1) (10,2) (10,3) (10,4) (10,5) (10,6) (10,7) (10,8) (10,9) (10,10) 

अब, वह कैसे काम करता है?

नहीं है कहा जाता है कोड , from0to10और बुला कोड । इस मामले में, यह ब्लॉक है जो निम्नानुसार है reset। बुलाया कोड में पारित मापदंडों में से एक एक वापसी पता है जो दिखाता है कि कॉलिंग कोड का क्या हिस्सा अभी तक निष्पादित नहीं किया गया है (**)। कॉलिंग कोड का वह हिस्सा निरंतरता है । कहा जाता है कि कोड उस पैरामीटर के साथ कर सकता है जो कुछ भी यह तय करता है: इसे करने के लिए नियंत्रण, या इसे अनदेखा करें, या इसे कई बार कॉल करें। यहां from0to10उस पूर्णांक के लिए निरंतरता को ०.१० कहा जाता है।

def from0to10() = shift { (cont: Int => Unit) =>
   for ( i <- 0 to 10 ) {
     cont(i) // call the continuation
   }
}

लेकिन निरंतरता कहां समाप्त होती है? यह महत्वपूर्ण है क्योंकि returnनिरंतरता से अंतिम नियंत्रण को कोड कहा जाता है from0to10। स्काला में, वह समाप्त होता है जहां resetब्लॉक समाप्त होता है (*)।

अब, हम देखते हैं कि निरंतरता के रूप में घोषित किया गया है cont: Int => Unit। क्यों? हम के from0to10रूप में आह्वान करते हैं val x = from0to10(), और Intउस मूल्य का प्रकार है जो जाता है xUnitइसका मतलब है कि ब्लॉक के बाद resetकोई मान नहीं लौटना चाहिए (अन्यथा एक प्रकार की त्रुटि होगी)। सामान्य तौर पर, 4 प्रकार के हस्ताक्षर होते हैं: फ़ंक्शन इनपुट, निरंतरता इनपुट, निरंतरता परिणाम, फ़ंक्शन परिणाम। चारों को आह्वान के संदर्भ से मेल खाना चाहिए।

ऊपर, हमने मानों के जोड़े मुद्रित किए। हमें गुणा तालिका प्रिंट करें। लेकिन हम \nप्रत्येक पंक्ति के बाद आउटपुट कैसे करते हैं ?

फ़ंक्शन backहमें यह निर्दिष्ट करने देता है कि नियंत्रण वापस आने पर क्या किया जाना चाहिए, निरंतरता से उस कोड तक जो इसे कहा जाता है।

def back(action: => Unit) = shift { (cont: Unit => Unit) =>
  cont()
  action
}

backपहले अपनी निरंतरता को बुलाता है, और फिर कार्रवाई करता है

reset {
  val i = from0to10()
  back { println() }
  val j = from0to10
  print(f"${i*j}%4d ") // printf-like formatted i*j
}

यह प्रिंट करता है:

   0    0    0    0    0    0    0    0    0    0    0 
   0    1    2    3    4    5    6    7    8    9   10 
   0    2    4    6    8   10   12   14   16   18   20 
   0    3    6    9   12   15   18   21   24   27   30 
   0    4    8   12   16   20   24   28   32   36   40 
   0    5   10   15   20   25   30   35   40   45   50 
   0    6   12   18   24   30   36   42   48   54   60 
   0    7   14   21   28   35   42   49   56   63   70 
   0    8   16   24   32   40   48   56   64   72   80 
   0    9   18   27   36   45   54   63   72   81   90 
   0   10   20   30   40   50   60   70   80   90  100 

खैर, अब कुछ ब्रेन-ट्विस्ट का समय है। के दो आह्वान हैं from0to10। पहले के लिए निरंतरता क्या है from0to10? यह इस प्रकार के आह्वान from0to10में बाइनरी कोड , लेकिन स्रोत कोड में भी काम बयान भी शामिल है val i =। यह समाप्त होता है जहां resetब्लॉक समाप्त होता है, लेकिन ब्लॉक का अंत resetपहले पर नियंत्रण वापस नहीं करता है from0to10resetब्लॉक का अंत 2 पर नियंत्रण लौटाता है from0to10, जो अंत में नियंत्रण को वापस लौटाता है back, और यह है backकि पहले के आह्वान पर नियंत्रण लौटाता है from0to10। जब पहला (हाँ! पहला!) from0to10बाहर निकलता है, तो पूरा resetब्लॉक बाहर निकल जाता है।

नियंत्रण वापस करने की ऐसी विधि को बैकट्रैकिंग कहा जाता है , यह एक बहुत पुरानी तकनीक है, जिसे कम से कम प्रोलॉग और एआई-उन्मुख लिस्प डेरिवेटिव के समय से जाना जाता है।

नाम resetऔर shiftमिथ्या नाम हैं। इन नामों को बिटवाइज़ ऑपरेशन के लिए बेहतर छोड़ दिया जाना चाहिए था। resetनिरंतरता सीमाओं को परिभाषित करता है, और shiftकॉल स्टैक से एक निरंतरता लेता है।

टिप्पणियाँ)

(*) स्काला में, जहां resetब्लॉक समाप्त होता है, निरंतरता समाप्त होती है। एक और संभावित दृष्टिकोण यह होगा कि इसे समाप्त करें जहां फ़ंक्शन समाप्त होता है।

(**) कॉल कोड के मापदंडों में से एक रिटर्न एड्रेस है जो दिखाता है कि कॉलिंग कोड के किस हिस्से को अभी तक निष्पादित नहीं किया गया है। खैर, स्काला में, रिटर्न एड्रेस का एक क्रम इसके लिए उपयोग किया जाता है। कितने? resetब्लॉक में प्रवेश करने के बाद से कॉल स्टैक पर रखे गए सभी वापसी पते ।


UPD भाग 2 जारी रखना: छानना

def onEven(x:Int) = shift { (cont: Unit => Unit) =>
  if ((x&1)==0) {
    cont() // call continuation only for even numbers
  }
}
reset {
  back { println() }
  val x = from0to10()
  onEven(x)
  print(s"$x ")
}

यह प्रिंट:

0 2 4 6 8 10 

आइए हम दो महत्वपूर्ण कार्यकलापों को कार्यान्वित करें: निरंतरता को त्यागना ( fail()) और उस पर नियंत्रण को पार करना ( succ()):

// fail: just discard the continuation, force control to return back
def fail() = shift { (cont: Unit => Unit) => }
// succ: does nothing (well, passes control to the continuation), but has a funny signature
def succ():Unit @cpsParam[Unit,Unit] = { }
// def succ() = shift { (cont: Unit => Unit) => cont() }

succ()(ऊपर) के दोनों संस्करण काम करते हैं। यह पता चला है कि shiftएक मजाकिया हस्ताक्षर है, और हालांकि succ()कुछ भी नहीं है, यह प्रकार संतुलन के लिए उस हस्ताक्षर होना चाहिए।

reset {
  back { println() }
  val x = from0to10()
  if ((x&1)==0) {
    succ()
  } else {
    fail()
  }
  print(s"$x ")
}

जैसा कि अपेक्षित है, यह प्रिंट करता है

0 2 4 6 8 10

एक फ़ंक्शन के भीतर, succ()आवश्यक नहीं है:

def onTrue(b:Boolean) = {
  if(!b) {
    fail()
  }
}
reset {
  back { println() }
  val x = from0to10()
  onTrue ((x&1)==0)
  print(s"$x ")
}

फिर से, यह प्रिंट करता है

0 2 4 6 8 10

अब, हम इसके onOdd()माध्यम से परिभाषित करते हैं onEven():

// negation: the hard way
class ControlTransferException extends Exception {}
def onOdd(x:Int) = shift { (cont: Unit => Unit) =>
  try {
    reset {
      onEven(x)
      throw new ControlTransferException() // return is not allowed here
    }
    cont()
  } catch {
    case e: ControlTransferException =>
    case t: Throwable => throw t
  }
}
reset {
  back { println() }
  val x = from0to10()
  onOdd(x)
  print(s"$x ")
}

ऊपर, अगर xयह भी है, तो एक अपवाद फेंक दिया जाता है और निरंतरता को नहीं कहा जाता है; यदि xविषम है, तो अपवाद नहीं फेंका जाता है और निरंतरता कहा जाता है। उपरोक्त कोड प्रिंट:

1 3 5 7 9 
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.