स्काला कैट्स / fs2 में स्टैक सेफ्टी के बारे में क्या कारण है?


13

यहाँ fs2 के लिए प्रलेखन से कोड का एक टुकड़ा है । फ़ंक्शन goपुनरावर्ती है। सवाल यह है कि हमें कैसे पता चलेगा कि यह स्टैक सुरक्षित है और यदि कोई फ़ंक्शन स्टैक सुरक्षित है तो इसका कारण क्या है?

import fs2._
// import fs2._

def tk[F[_],O](n: Long): Pipe[F,O,O] = {
  def go(s: Stream[F,O], n: Long): Pull[F,O,Unit] = {
    s.pull.uncons.flatMap {
      case Some((hd,tl)) =>
        hd.size match {
          case m if m <= n => Pull.output(hd) >> go(tl, n - m)
          case m => Pull.output(hd.take(n.toInt)) >> Pull.done
        }
      case None => Pull.done
    }
  }
  in => go(in,n).stream
}
// tk: [F[_], O](n: Long)fs2.Pipe[F,O,O]

Stream(1,2,3,4).through(tk(2)).toList
// res33: List[Int] = List(1, 2)

यदि हम goकिसी अन्य विधि से कॉल करते हैं तो क्या यह भी सुरक्षित होगा ?

def tk[F[_],O](n: Long): Pipe[F,O,O] = {
  def go(s: Stream[F,O], n: Long): Pull[F,O,Unit] = {
    s.pull.uncons.flatMap {
      case Some((hd,tl)) =>
        hd.size match {
          case m if m <= n => otherMethod(...)
          case m => Pull.output(hd.take(n.toInt)) >> Pull.done
        }
      case None => Pull.done
    }
  }

  def otherMethod(...) = {
    Pull.output(hd) >> go(tl, n - m)
  }

  in => go(in,n).stream
}

नहीं, बिल्कुल नहीं। हालांकि अगर यह पूंछ पुनरावृत्ति का मामला है तो कृपया बताएं, लेकिन ऐसा लगता है कि यह नहीं है। जहाँ तक मुझे पता है कि बिल्लियाँ कुछ जादू करती हैं जिन्हें स्टैम्प सुरक्षा सुनिश्चित करने के लिए ट्रम्पोलिंग कहा जाता है। दुर्भाग्य से मैं यह नहीं बता सकता कि कब कोई फंक्शनल ट्रम्पोलाइज्ड है और कब नहीं।
लेव डेनिसोव

आप goउदाहरण के लिए Monad[F]टाइपकास्टल का उपयोग करने के लिए फिर से लिख सकते हैं - ऐसी tailRecMविधि है जो आपको स्पष्ट रूप से गारंटी देने के लिए ट्रम्पोलिन प्रदर्शन करने की अनुमति देती है कि फ़ंक्शन सुरक्षित हो जाएगा। मैं गलत हो सकता हूं, लेकिन इसके बिना आप Fअपने दम पर सुरक्षित होने पर भरोसा कर रहे हैं (जैसे अगर यह आंतरिक रूप से trampoline को लागू करता है), लेकिन आप कभी नहीं जानते कि कौन आपके परिभाषित करेगा F, इसलिए आपको ऐसा नहीं करना चाहिए। यदि आपके पास कोई गारंटी नहीं है कि Fस्टैक सुरक्षित है, तो एक प्रकार के वर्ग का उपयोग करें जो प्रदान करता है tailRecMक्योंकि यह कानून द्वारा स्टैक-सुरक्षित है।
मेटुस कुब्जोक 21

1
कम्पाइलर @tailrecको टेल री फ़ंक्शन के लिए एनोटेशन के साथ यह साबित करना आसान है । अन्य मामलों के लिए स्काला AFAIK में कोई औपचारिक गारंटी नहीं है। यहां तक ​​कि अगर फ़ंक्शन स्वयं सुरक्षित है, तो अन्य फ़ंक्शन जो कॉल कर रहे हैं वे नहीं हो सकते हैं: /।
y --s .la

जवाबों:


17

मेरे पिछले जवाब यहाँ कुछ पृष्ठभूमि जानकारी है कि उपयोगी हो सकता है देता है। मूल विचार यह है कि कुछ प्रभाव प्रकारों में ऐसे flatMapकार्यान्वयन होते हैं जो स्टैक-सुरक्षित पुनरावृत्ति का समर्थन करते हैं - आप flatMapया तो स्पष्ट रूप से या पुनरावृत्ति के माध्यम से कॉल कर सकते हैं जैसे कि आप चाहते हैं और आप स्टैक को ओवरफ्लो नहीं करेंगे।

कुछ प्रभाव प्रकारों के लिए flatMapस्टैक-सुरक्षित होना संभव नहीं है , क्योंकि प्रभाव के शब्दार्थ के कारण। अन्य मामलों में स्टैक-सेफ लिखना संभव हो सकता है flatMap, लेकिन कार्यान्वयनकर्ताओं ने प्रदर्शन या अन्य कारणों के कारण निर्णय नहीं लिया होगा।

दुर्भाग्य से यह जानने के flatMapलिए कोई मानक (या यहां तक ​​कि पारंपरिक) तरीका नहीं है कि किसी दिए गए प्रकार के लिए स्टैक सुरक्षित है या नहीं। बिल्लियों में एक tailRecMऑपरेशन शामिल होता है जो किसी भी वैध मोनैडिक प्रभाव प्रकार के लिए स्टैक-सुरक्षित मोनैडिक पुनरावृत्ति प्रदान करना चाहिए, और कभी-कभी एक tailRecMकार्यान्वयन को देखने के लिए जिसे कानून के रूप में जाना जाता है, एक flatMapस्टैक-सुरक्षित है के बारे में कुछ संकेत प्रदान कर सकता है। के मामले में Pullयह कैसा दिखता इस :

def tailRecM[A, B](a: A)(f: A => Pull[F, O, Either[A, B]]) =
  f(a).flatMap {
    case Left(a)  => tailRecM(a)(f)
    case Right(b) => Pull.pure(b)
  }

यह tailRecMबस के माध्यम से recursing है flatMap, और हम जानते हैं कि Pullके Monadउदाहरण वैध है , जो बहुत अच्छी सबूत है कि है Pullकी flatMapहै ढेर-सुरक्षित। यहाँ कारक उलझी है कि के लिए उदाहरण है Pullएक है ApplicativeErrorपर बाधा Fहै कि Pullकी flatMapनहीं है, लेकिन इस मामले कि कुछ भी नहीं बदलता है में।

तो tkकार्यान्वयन यहां ढेर-सुरक्षित है क्योंकि flatMapपर Pullढेर-सुरक्षित है, और हम जानते हैं कि अपने को देख से tailRecMकार्यान्वयन। (अगर हम एक छोटे से गहरा खोदा हम यह पता लगाने सकता है कि flatMapढेर-सुरक्षित है क्योंकि Pullअनिवार्य रूप से करने के लिए एक आवरण है FreeC, जो है trampolined ।)

यह शायद के tkसंदर्भ में फिर से लिखना बहुत कठिन नहीं होगा tailRecM, हालांकि हमें अन्यथा अनावश्यक ApplicativeErrorबाधा को जोड़ना होगा । मैं प्रलेखन के लेखकों अनुमान लगा रहा हूँ करने के लिए नहीं है कि स्पष्टता के लिए चुना है, और क्योंकि वे जानते थे कि Pull's flatMapठीक है।


अद्यतन: यहाँ एक काफी यांत्रिक tailRecMअनुवाद है:

import cats.ApplicativeError
import fs2._

def tk[F[_], O](n: Long)(implicit F: ApplicativeError[F, Throwable]): Pipe[F, O, O] =
  in => Pull.syncInstance[F, O].tailRecM((in, n)) {
    case (s, n) => s.pull.uncons.flatMap {
      case Some((hd, tl)) =>
        hd.size match {
          case m if m <= n => Pull.output(hd).as(Left((tl, n - m)))
          case m => Pull.output(hd.take(n.toInt)).as(Right(()))
        }
      case None => Pull.pure(Right(()))
    }
  }.stream

ध्यान दें कि कोई स्पष्ट पुनरावृत्ति नहीं है।


आपके दूसरे प्रश्न का उत्तर इस बात पर निर्भर करता है कि दूसरी विधि कैसी दिखती है, लेकिन आपके विशिष्ट उदाहरण के मामले में, >>बस अधिक flatMapपरतों में परिणाम होगा , इसलिए यह ठीक होना चाहिए।

अपने प्रश्न को और अधिक आम तौर पर संबोधित करने के लिए, यह पूरा विषय स्काला में एक गड़बड़ गड़बड़ है। आपको कार्यान्वयन में खुदाई करने की ज़रूरत नहीं है, जैसा कि हमने यह जानने के लिए ऊपर किया है कि क्या एक प्रकार स्टैक-सुरक्षित मोनडिक पुनरावृत्ति का समर्थन करता है या नहीं। प्रलेखन के आसपास बेहतर सम्मेलनों यहाँ एक मदद होगी, लेकिन दुर्भाग्य से हम उस का एक बहुत अच्छा काम नहीं कर रहे हैं। आप हमेशा tailRecM"सुरक्षित" होने का उपयोग कर सकते हैं (जो कि F[_]सामान्य होने पर, वैसे भी आप क्या करना चाहते हैं ), लेकिन तब भी आप भरोसा कर रहे हैं कि Monadकार्यान्वयन कानूनन सही है।

योग करने के लिए: यह चारों ओर एक बुरी स्थिति है, और संवेदनशील स्थितियों में आपको यह सुनिश्चित करने के लिए अपने स्वयं के परीक्षण लिखने चाहिए कि इस तरह से कार्यान्वयन स्टैक-सुरक्षित हैं।


विवरण के लिए आपका धन्यवाद। सवाल के बारे में जब हम goकिसी अन्य विधि से कॉल करते हैं, तो यह असुरक्षित क्या बना सकता है? यदि हम कॉल करने Pull.output(hd) >> go(tl, n - m)से पहले कुछ गैर-पुनरावर्ती गणना करते हैं तो क्या यह ठीक है?
लेव

हां, यह ठीक होना चाहिए (यह मानते हुए कि गणना स्वयं स्टैक को ओवरफ्लो नहीं करती है, निश्चित रूप से)।
ट्रैविस ब्राउन

उदाहरण के लिए, कौन सा प्रभाव प्रकार, स्टैड-सिक्योर नहीं होगा? निरंतरता प्रकार?
बॉब

राइट @bob, हालांकि बिल्लियों के ContTकी flatMap है वास्तव में ढेर सुरक्षित (एक के माध्यम से Deferअंतर्निहित प्रकार पर बाधा)। मैं कुछ और की तरह सोच रहा था List, जहां से गुजरना flatMapस्टैक-सेफ नहीं है (यह एक वैध है tailRecM, हालांकि)।
ट्रैविस ब्राउन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.