2011-12-18 21 views
9

निम्नलिखित blog article दिखाता है कि एफ # foldBack में निरंतरता गुजर शैली का उपयोग पूंछ पुनरावर्ती बनाया जा सकता है।क्या फोल्ड राइट पूंछ रिकर्सिव बनाने के लिए निरंतरता का उपयोग करना संभव है?

स्काला में यह इसका मतलब यह होगा:

def foldBack[T,U](l: List[T], acc: U)(f: (T, U) => U): U = { 
    l match { 
    case x :: xs => f(x, foldBack(xs, acc)(f)) 
    case Nil => acc 
    } 
} 

ऐसा करके पूंछ पुनरावर्ती बनाया जा सकता है:

def foldCont[T,U](list: List[T], acc: U)(f: (T, U) => U): U = { 
    @annotation.tailrec 
    def loop(l: List[T], k: (U) => U): U = { 
    l match { 
     case x :: xs => loop(xs, (racc => k(f(x, racc)))) 
     case Nil => k(acc) 
    } 
    } 
    loop(list, u => u) 
} 

दुर्भाग्य से, मैं अभी भी लंबी सूची के लिए एक ढेर अतिप्रवाह मिलता है। लूप पूंछ रिकर्सिव और अनुकूलित है लेकिन मुझे लगता है कि स्टैक संचय केवल निरंतर कॉल में स्थानांतरित हो गया है।

क्यों एफ # के साथ एक समस्या नहीं है? और क्या इस के आसपास स्कैला के साथ काम करने का कोई तरीका है?

संपादित: यहाँ कुछ कोड है कि ढेर की गहराई पता चलता है:

def showDepth(s: Any) { 
    println(s.toString + ": " + (new Exception).getStackTrace.size) 
} 

def foldCont[T,U](list: List[T], acc: U)(f: (T, U) => U): U = { 
    @annotation.tailrec 
    def loop(l: List[T], k: (U) => U): U = { 
    showDepth("loop") 
    l match { 
     case x :: xs => loop(xs, (racc => { showDepth("k"); k(f(x, racc)) })) 
     case Nil => k(acc) 
    } 
    } 
    loop(list, u => u) 
} 

foldCont(List.fill(10)(1), 0)(_ + _) 

यह प्रिंट:

loop: 50 
loop: 50 
loop: 50 
loop: 50 
loop: 50 
loop: 50 
loop: 50 
loop: 50 
loop: 50 
loop: 50 
loop: 50 
k: 51 
k: 52 
k: 53 
k: 54 
k: 55 
k: 56 
k: 57 
k: 58 
k: 59 
k: 60 
res2: Int = 10 
+0

यह समझ में नहीं आता है। क्या आपके पास एक साधारण परीक्षण मामला है? –

+0

@ डैनियल सी। सोब्राल, मैंने जो कोड जोड़ा है उसे देखें और यह प्रिंट करता है। – huynhjl

उत्तर

4

समस्या निरंतरता समारोह (racc => k(f(x, racc))) ही है। इस पूरे व्यवसाय के लिए काम करने के लिए इसे tailcall अनुकूलित किया जाना चाहिए, लेकिन नहीं है।

स्कैला मनमाने ढंग से पूंछ कॉल के लिए tailcall अनुकूलन नहीं कर सकता है, केवल उन लोगों के लिए जो लूप में बदल सकते हैं (यानी जब फ़ंक्शन स्वयं कॉल करता है, कुछ अन्य फ़ंक्शन नहीं)।

+0

यही मैंने अनुमान लगाया है। क्या कुछ और हो सकता है? Trampolines जैसे कुछ का उपयोग करने की तरह? – huynhjl

+0

ट्रैम्पोलिन्स शायद मदद करेंगे, लेकिन मुझे लगता है कि इस विशेष मामले में 'बाएंफोल्ड' समस्या को कम दर्द के साथ हल करेगा। यदि आप किसी कारण से बिल्कुल 'फ़ोल्ड राइट' अर्थशास्त्र चाहते हैं, तो आप सूची को उलट सकते हैं और परिणाम पर 'foldLeft' को कॉल कर सकते हैं। –

+1

यह पता चलता है कि वास्तव में यह इस मामले में दर्दनाक नहीं है, मेरा अपना जवाब देखें। – huynhjl

4

यह F # के साथ कोई समस्या क्यों नहीं है?

एफ # सभी पूंछ कॉल अनुकूलित है।

और क्या स्कैला के साथ इस पर काम करने का कोई तरीका है?

आप ट्रैम्पोलाइंस की तरह अन्य तकनीकों का उपयोग TCO कर सकते हैं, लेकिन आप इंटरॉप खो क्योंकि यह बुला सम्मेलन बदलता है और यह है ~ 10 × धीमी। यह तीन कारणों में से एक है जिसे मैं स्कैला का उपयोग नहीं करता हूं।

संपादित

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

1,000x एक 1.67GHz N570 इंटेल एटॉम के साथ अपने नेटबुक पर एक 1,000 तत्व सूची में के लिए, मैं मिलता है:

List.fold  0.022s 
List.rev+fold 0.116s 
List.foldBack 0.047s 
foldContTC 0.334s 

1x 1,000,000 तत्व सूची के लिए, मैं:

List.fold  0.024s 
List.rev+fold 0.188s 
List.foldBack 0.054s 
foldContTC 0.570s 

ऑप्टिकल पूंछ रिकर्सिव वाले लोगों के साथ ओकैमल के गैर-पूंछ-रिकर्सिव सूची फ़ंक्शंस को बदलने के संदर्भ में आप कैमल-सूची पर इस बारे में पुरानी चर्चाओं में रुचि भी ले सकते हैं।

+3

स्कैला का उपयोग नहीं करने वाले अन्य दो कारण क्या हैं? –

+3

@StephenSwensen: मूल्य प्रकारों की कमी और प्रकार अनुमान की कमी। ध्यान दें कि पूंछ कॉल और मूल्य प्रकारों की कमी JVM के साथ एक समस्या है और स्कैला नहीं है। ये भी कारण हैं कि मैंने जेवीएम की बजाय एलएलवीएम पर एचएलवीएम विकसित करना चुना। एलएलवीएम को स्कैला पोर्ट करने के लिए जेफ रेडी की परियोजना में इन दोनों समस्याओं को ठीक करने की क्षमता है, जो बिल्कुल शानदार होगा। –

6

जॉन, एनएम, आपके उत्तरों के लिए धन्यवाद। आपकी टिप्पणियों के आधार पर मैंने सोचा कि मैं कोशिश करता हूं और ट्रैम्पोलिन का उपयोग करता हूं। कुछ शोध से पता चलता है कि स्कैला में TailCalls में ट्रैम्पोलिन के लिए लाइब्रेरी समर्थन है। यहाँ है कि मैं क्या लगभग नगण्य का एक सा के बाद के साथ आया है:

def foldContTC[T,U](list: List[T], acc: U)(f: (T, U) => U): U = { 
    import scala.util.control.TailCalls._ 
    @annotation.tailrec 
    def loop(l: List[T], k: (U) => TailRec[U]): TailRec[U] = { 
    l match { 
     case x :: xs => loop(xs, (racc => tailcall(k(f(x, racc))))) 
     case Nil => k(acc) 
    } 
    } 
    loop(list, u => done(u)).result 
} 

मैं यह कैसे ट्रैम्पोलिन के बिना समाधान के साथ-साथ डिफ़ॉल्ट foldLeft और foldRight कार्यान्वयन से तुलना को देखने के लिए दिलचस्पी थी। यहाँ बेंचमार्क कोड और कुछ परिणाम है:

val size = 1000 
val list = List.fill(size)(1) 
val warm = 10 
val n = 1000 
bench("foldContTC", warm, lots(n, foldContTC(list, 0)(_ + _))) 
bench("foldCont", warm, lots(n, foldCont(list, 0)(_ + _))) 
bench("foldRight", warm, lots(n, list.foldRight(0)(_ + _))) 
bench("foldLeft", warm, lots(n, list.foldLeft(0)(_ + _))) 
bench("foldLeft.reverse", warm, lots(n, list.reverse.foldLeft(0)(_ + _))) 

समय कर रहे हैं:

foldContTC: warming... 
Elapsed: 0.094 
foldCont: warming... 
Elapsed: 0.060 
foldRight: warming... 
Elapsed: 0.160 
foldLeft: warming... 
Elapsed: 0.076 
foldLeft.reverse: warming... 
Elapsed: 0.155 

इस आधार पर, यह प्रतीत होता है कि ट्रैम्पोलाइनिंग वास्तव में बहुत अच्छा प्रदर्शन उपज है। मुझे संदेह है कि मुक्केबाजी/अनबॉक्सिंग के शीर्ष पर जुर्माना अपेक्षाकृत बुरा नहीं है।

संपादित करें: के रूप में जॉन की टिप्पणी ने सुझाव दिया है, यहाँ 1M आइटम जो पुष्टि करते हैं कि प्रदर्शन बड़ा सूचियों के साथ खराब हो पर समय कर रहे हैं। इसके अलावा मुझे पता चला है कि पुस्तकालय List.foldLeft कार्यान्वयन ओवरराइड नहीं कर रहा है, तो मैं निम्नलिखित foldLeft2 साथ समय समाप्त हो गया:

def foldLeft2[T,U](list: List[T], acc: U)(f: (T, U) => U): U = { 
    list match { 
    case x :: xs => foldLeft2(xs, f(x, acc))(f) 
    case Nil => acc 
    } 
} 

val size = 1000000 
val list = List.fill(size)(1) 
val warm = 10 
val n = 2 
bench("foldContTC", warm, lots(n, foldContTC(list, 0)(_ + _))) 
bench("foldLeft", warm, lots(n, list.foldLeft(0)(_ + _))) 
bench("foldLeft2", warm, lots(n, foldLeft2(list, 0)(_ + _))) 
bench("foldLeft.reverse", warm, lots(n, list.reverse.foldLeft(0)(_ + _))) 
bench("foldLeft2.reverse", warm, lots(n, foldLeft2(list.reverse, 0)(_ + _))) 

पैदावार:

foldContTC: warming... 
Elapsed: 0.801 
foldLeft: warming... 
Elapsed: 0.156 
foldLeft2: warming... 
Elapsed: 0.054 
foldLeft.reverse: warming... 
Elapsed: 0.808 
foldLeft2.reverse: warming... 
Elapsed: 0.221 

तो foldLeft2.reverse विजेता है ...

+0

"बहुत अच्छा प्रदर्शन"। वास्तव में। मैं उस संदिग्ध रूप से अच्छा प्रदर्शन कहूंगा! शायद ट्रैम्पोलिन कार्यान्वयन यह समझने के लिए पर्याप्त चालाक है कि इसे लात मारना नहीं है क्योंकि आपकी सूचियां इतनी छोटी हैं? 1 एम-एलिमेंट सूचियों के साथ आपको क्या प्रदर्शन माप मिलते हैं? –

+0

बंद होने वाले समय के साथ, कैश और जीसी मुद्दे भी खेलेंगे, उदाहरण के लिए एक ही 1k-element सूची को उलटकर एक पीढ़ी जीसी और कैश कुशल के साथ सस्ता है लेकिन 1 एम-एलिमेंट सूचियां नर्सरी या थ्रेड-लोकल क्षेत्र से बचने की संभावना है जो इसके ओवरहेड और कैश दक्षता को कम कर देगा। –

+0

@ जोनह्रोप, अच्छी कॉल, 1 एम तत्वों के लिए जोड़ा गया समय। – huynhjl

3

मुझे इस प्रश्न का देर हो चुकी है, लेकिन मैं यह दिखाना चाहता था कि आप एक पूर्ण ट्रैम्पोलिन का उपयोग किए बिना पूंछ-रिकर्सिव फ़ोल्डराइट कैसे लिख सकते हैं;

object FoldRight { 

    def apply[A, B](list: Seq[A])(init: B)(f: (A, B) => B): B = { 
    @scala.annotation.tailrec 
    def step(current: Seq[A], conts: List[B => B]): B = current match { 
     case Seq(last) => conts.foldLeft(f(last, init)) { (acc, next) => next(acc) } 
     case Seq(x, xs @ _*) => step(xs, { acc: B => f(x, acc) } +: conts) 
     case Nil => init 
    } 
    step(list, Nil) 
    } 

} 
: निरंतरता की एक सूची जमा (के बजाय उन्हें एक दूसरे, जब किया जिसमें स्टैक अतिप्रवाह की ओर जाता है फोन वाले) और अंत में उन पर तह, की तरह तरह एक ढेर में रखते हुए, लेकिन ढेर पर से

अंत में जो गुना होता है वह स्वयं पूंछ-पुनरावर्ती होता है। इसे आजमाएं in ScalaFiddle

प्रदर्शन के संदर्भ में, यह पूंछ कॉल संस्करण से थोड़ा खराब प्रदर्शन करता है।

[info] Benchmark   (length) Mode Cnt Score Error Units 
[info] FoldRight.conts   100 avgt 30 0.003 ± 0.001 ms/op 
[info] FoldRight.conts   10000 avgt 30 0.197 ± 0.004 ms/op 
[info] FoldRight.conts  1000000 avgt 30 77.292 ± 9.327 ms/op 
[info] FoldRight.standard  100 avgt 30 0.002 ± 0.001 ms/op 
[info] FoldRight.standard  10000 avgt 30 0.154 ± 0.036 ms/op 
[info] FoldRight.standard 1000000 avgt 30 18.796 ± 0.551 ms/op 
[info] FoldRight.tailCalls  100 avgt 30 0.002 ± 0.001 ms/op 
[info] FoldRight.tailCalls  10000 avgt 30 0.176 ± 0.004 ms/op 
[info] FoldRight.tailCalls 1000000 avgt 30 33.525 ± 1.041 ms/op 
संबंधित मुद्दे