अनिवार्य ब्रेक स्टेटमेंट और अन्य लूप चेक के कार्यात्मक समकक्ष क्या हैं?


36

चलो कहते हैं, मैं नीचे तर्क है। कैसे लिखें कि कार्यात्मक प्रोग्रामिंग में?

    public int doSomeCalc(int[] array)
    {
        int answer = 0;
        if(array!=null)
        {
            for(int e: array)
            {
                answer += e;
                if(answer == 10) break;
                if(answer == 150) answer += 100;
            }
        }
        return answer;
    }

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

मुझे पता है कि उपरोक्त कोड पूरी तरह से जरूरी है। भविष्य में इसे एफपी पर स्थानांतरित करने के पूर्व विचार के साथ नहीं लिखा गया था।


1
ध्यान दें कि संयोजन breakऔर लूप के अंदर से return answerबदला जा सकता है return। एफपी में आप निरंतरता का उपयोग करके इस शुरुआती रिटर्न को लागू कर सकते हैं, उदाहरण के लिए देखें en.wikipedia.org/wiki/Continuation
जियोर्जियो

1
@ जियोर्जियो निरंतरता यहाँ एक बहुत बड़ा ओवरकिल होगा। यह वैसे भी एक लूप है, इसके अगले पुनरावृत्ति को कॉल करने के लिए आप एक टेल कॉल करते हैं, इसलिए आपको तोड़ने के लिए इसे अब और कॉल न करें और बस उत्तर वापस करें। के लिए नेस्टेड छोरों, या अन्य जटिल नियंत्रण प्रवाह होगा, तो इस आप अपने कोड पुनर्गठन करने के लिए heaving बजाय निरंतरता का उपयोग कर सकते हैं जहां उपयोग करने के लिए सरल तकनीक (जो हमेशा संभव होना चाहिए, लेकिन बहुत जटिल कोड संरचना को जन्म दे सकती ऊपर जो कम या ज्यादा होता बयान निरंतरता; और एक से अधिक निकास बिंदु के लिए आपको निश्चित रूप से उनकी आवश्यकता होगी)।
नेस

8
इस मामले में takeWhile:।
योनातन

1
@WillNess: मैं सिर्फ इसका उल्लेख करना चाहता था क्योंकि इसका उपयोग किसी भी बिंदु पर एक जटिल गणना छोड़ने के लिए किया जा सकता है। यह ओपी के ठोस उदाहरण के लिए शायद सबसे अच्छा समाधान नहीं है।
जियोर्जियो

@ जियोर्जियो आप सही कह रहे हैं, यह सामान्य रूप से सबसे व्यापक है। वास्तव में यह प्रश्न बहुत व्यापक है, IYKWIM (यानी एक दिल की धड़कन में SO पर बंद हो जाएगा)।
विल नेस

जवाबों:


45

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

doSomeCalc :: [Int] -> Int
doSomeCalc values = foldr1 combine values
  where combine v1 v2 | v1 == 10  = v1
                      | v1 == 150 = v1 + 100 + v2
                      | otherwise = v1 + v2

यदि आप हास्केल के वाक्य-विन्यास से परिचित नहीं हैं, तो लाइन से इस डाउन लाइन को तोड़ना इस तरह काम करता है:

doSomeCalc :: [Int] -> Int

फ़ंक्शन के प्रकार को परिभाषित करता है, किलों की एक सूची को स्वीकार करता है और एक एकल इंट वापस करता है।

doSomeCalc values = foldr1 combine values

फ़ंक्शन का मुख्य निकाय: दिया गया तर्क values, foldr1तर्कों के साथ बुलाया गया रिटर्न combine(जिसे हम नीचे परिभाषित करेंगे) और valuesfoldr1तह आदिम का एक प्रकार है जो सूची के पहले मूल्य (इसलिए 1फ़ंक्शन नाम में) के लिए सेट किए गए संचायक के साथ शुरू होता है , फिर इसे बाएं से दाएं उपयोगकर्ता के निर्दिष्ट फ़ंक्शन का उपयोग करके जोड़ती है (जिसे आमतौर पर दाएं गुना कहा जाता है ,) इसलिए rफ़ंक्शन नाम में)। तो (या अधिक पारंपरिक सी-जैसे सिंटैक्स में) के foldr1 f [1,2,3]बराबर है ।f 1 (f 2 3)f(1,f(2,3))

  where combine v1 v2 | v1 == 10  = v1

combineस्थानीय फ़ंक्शन को परिभाषित करना : इसे दो तर्क मिलते हैं, v1और v2। जब v110 साल का होता है, तो यह वापस आ जाता है v1। इस मामले में, v2 का मूल्यांकन कभी नहीं किया जाता है , इसलिए लूप यहां बंद हो जाता है।

                      | v1 == 150 = v1 + 100 + v2

वैकल्पिक रूप से, जब v1 150 है, तो इसमें अतिरिक्त 100 जोड़ा जाता है, और v2 जोड़ता है।

                      | otherwise = v1 + v2

और, यदि उन स्थितियों में से कोई भी सत्य नहीं है, तो बस v1 से v2 जोड़ता है।

अब, यह समाधान हास्केल के लिए कुछ हद तक विशिष्ट है, क्योंकि यह तथ्य कि एक सही गुना समाप्त हो जाता है यदि संयोजन फ़ंक्शन अपने दूसरे तर्क का मूल्यांकन नहीं करता है, तो हास्केल की आलसी मूल्यांकन रणनीति के कारण होता है। मैं क्लजुरे को नहीं जानता, लेकिन मेरा मानना ​​है कि यह सख्त मूल्यांकन का उपयोग करता है, इसलिए मैं foldइसकी मानक लाइब्रेरी में एक समारोह होने की उम्मीद करूंगा जिसमें शुरुआती समाप्ति के लिए विशिष्ट समर्थन शामिल है। यह अक्सर कहा जाता है foldWhile, foldUntilया समान है।

क्लोजर लाइब्रेरी डॉक्यूमेंटेशन पर एक त्वरित नज़र रखने से पता चलता है कि यह नामकरण में सबसे अधिक कार्यात्मक भाषाओं से थोड़ा अलग है, और यह foldवह नहीं है जो आप खोज रहे हैं (यह समानांतर गणना को सक्षम करने के उद्देश्य से अधिक उन्नत तंत्र है) लेकिन reduceअधिक प्रत्यक्ष है बराबर। प्रारंभिक समाप्ति तब होती है यदि reducedफ़ंक्शन आपके संयोजन फ़ंक्शन के भीतर कहा जाता है। मुझे 100% यकीन नहीं है कि मैं वाक्य रचना को समझता हूं, लेकिन मुझे संदेह है कि आप जो खोज रहे हैं वह कुछ इस तरह है:

(reduce 
    (fn [v1 v2]
        (if (= v1 10) 
             (reduced v1)
             (+ v1 v2 (if (= v1 150) 100 0))))
    array)

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


11
नाम v1 v2भ्रमित कर रहे हैं: v1"सरणी से मान" है, लेकिन v2संचित परिणाम है। और आपका अनुवाद त्रुटिपूर्ण है, मेरा मानना ​​है कि ओपी का लूप तब निकलता है जब संचित (बाएं से) मान 10 मारता है, न कि कुछ तत्व। 100 की वृद्धि के साथ ही। यदि यहाँ सिलवटों का उपयोग करना है, तो जल्दी निकलने के लिए बाईं ओर का उपयोग करें, foldlWhile यहाँ पर कुछ भिन्नता है
विल नेस

2
यह अजीब है कि एसई पर सबसे गलत उत्तर कैसे प्राप्त होता है .... यह गलतियाँ करने के लिए ठीक है, आप अच्छी कंपनी में हैं :) , भी। लेकिन एसओ / एसई पर ज्ञान खोज तंत्र निश्चित रूप से टूट गया है।
विल नेस

1
क्लोजर कोड लगभग सही है, लेकिन (= v1 150)इससे पहले v2(उर्फ e) मूल्य का उपयोग करने की शर्त को इसके लिए सारांशित किया गया है।
निकोनिरह

1
Breaking this down line by line in case you're not familiar with Haskell's syntax-- आप मेरे हीरो हैं। हास्केल मेरे लिए एक रहस्य है।
कप्तान मैन

15
@WillNess यह upvoted है क्योंकि यह सबसे तुरंत समझने योग्य अनुवाद और स्पष्टीकरण है। तथ्य यह है कि यह गलत है शर्म की बात है, लेकिन यहाँ अपेक्षाकृत महत्वहीन है क्योंकि थोड़ी सी भी त्रुटि इस तथ्य को नकारती नहीं है कि उत्तर अन्यथा सहायक है। लेकिन निश्चित रूप से इसे सही किया जाना चाहिए।
कोनराड रुडोल्फ

33

आप इसे आसानी से पुनरावृत्ति में बदल सकते हैं। और इसमें अच्छी पूंछ-अनुकूलित पुनरावर्ती कॉल है।

स्यूडोकोड:

public int doSomeCalc(int[] array)
{
    return doSomeCalcInner(array, 0);
}

public int doSomeCalcInner(int[] array, int answer)
{
    if (array is empty) return answer;

    // not sure how to efficiently implement head/tails array split in clojure
    var head = array[0] // first element of array
    var tail = array[1..] // remainder of array

    answer += head;
    if (answer == 10) return answer;
    if (answer == 150) answer += 100;

    return doSomeCalcInner(tail, answer);
}

14
हां। लूप के बराबर कार्यात्मक पूंछ-पुनरावृत्ति है, और एक सशर्त के बराबर कार्यात्मक अभी भी एक सशर्त है।
जोर्ग डब्ल्यू मित्तग

4
@ JörgWMittag मैं कहूंगा कि पूंछ की पुनरावृत्ति कार्यात्मक समकक्ष है GOTO। (काफी बुरा नहीं है, लेकिन अभी भी बहुत अजीब है।) एक लूप के बराबर, जैसा कि जूल्स कहते हैं, एक उपयुक्त गुना है।
लेफ्टनैबरबाउट

6
@leftractionabout मैं वास्तव में असहमत हूँ। मैं कहता हूं कि पूंछ पुनरावृत्ति एक गोटो की तुलना में अधिक विवश है, इसे केवल और केवल पूंछ की स्थिति में कूदने की आवश्यकता है। यह मौलिक रूप से एक लूपिंग निर्माण के बराबर है। मैं कहूंगा कि सामान्य रूप से पुनरावृत्ति के बराबर है GOTO। किसी भी मामले में, जब आप पूंछ पुनरावृत्ति संकलित करते हैं, तो यह केवल while (true)फ़ंक्शन बॉडी के साथ एक लूप के लिए उबलता है जहां शुरुआती रिटर्न केवल एक breakबयान है। एक गुना, जबकि आप इसे लूप होने के बारे में सही हैं, वास्तव में एक सामान्य लूपिंग निर्माण की तुलना में अधिक विवश है; यह प्रत्येक लूप के लिए अधिक पसंद है
J_mie6

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

1
... जैसा कि सिलवटों के लिए: आप सही कह रहे हैं, एक पारंपरिक गुना (कैटामोर्फिज्म) एक बहुत ही विशिष्ट प्रकार का लूप है, लेकिन इन पुनरावर्ती योजनाओं को सामान्यीकृत किया जा सकता है (एना- / एपीओ- / हिलेरोम्फिज़्म); सामूहिक रूप से ये IMO अनिवार्य लूप के लिए उचित प्रतिस्थापन हैं।
लेफ्टनैबरैबाउट

13

मैं वास्तव में जूल्स के जवाब को पसंद करता हूं , लेकिन मैं इसके अलावा कुछ ऐसे लोगों को इंगित करना चाहता था जो अक्सर आलसी कार्यात्मक प्रोग्रामिंग के बारे में याद करते हैं, जो यह है कि सब कुछ "पाश के अंदर" नहीं होना चाहिए। उदाहरण के लिए:

baseSums = scanl (+) 0

offsets = scanl (\offset sum -> if sum == 150 then offset + 100 else offset) 0

zipWithOffsets xs = zipWith (+) xs (offsets xs)

stopAt10 xs = if 10 `elem` xs then 10 else last xs

result = stopAt10 . zipWithOffsets . baseSums

result [1..]         -- 10
result [11..1000000] -- 500000499945

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


तर्क यहाँ सब जगह बिखरा हुआ है। इस कोड को बनाए रखना आसान नहीं होगा। stopAt10है एक अच्छा उपभोक्ता। यदि आपका जवाब है एक आप में है कि इसे सही ढंग से बुनियादी निर्माता को अलग कर अदालत में तलब की तुलना में बेहतर scanl (+) 0मूल्यों की। उनके उपभोग को नियंत्रण तर्क को सीधे शामिल करना चाहिए, हालांकि, केवल दो spanएस और ए के साथ बेहतर तरीके से लागू किया गया है last। वह मूल कोड संरचना और तर्क का भी बारीकी से पालन करेगा, और बनाए रखना आसान होगा।
विल नेस

6

अधिकांश सूची प्रसंस्करण उदाहरण आप की तरह उपयोग कार्यों देखेंगे map, filter, sumआदि जो एक पूरे के रूप सूची पर कार्य करते हैं। लेकिन आपके मामले में आपके पास सशर्त प्रारंभिक निकास है - एक असामान्य पैटर्न जो सामान्य सूची संचालन द्वारा समर्थित नहीं है। तो आपको एक अमूर्त स्तर को नीचे गिराने और पुनरावृत्ति का उपयोग करने की आवश्यकता है - जो कि कैसे आवश्यक है कि कैसे निकटता का उदाहरण दिखता है।

यह क्लोजर में एक सीधा (शायद मुहावरेदार नहीं) अनुवाद है:

(defn doSomeCalc 
  ([lst] (doSomeCalc lst 0))
  ([lst sum]
    (if (empty? lst) sum
        (if (= sum 10) sum
            (let [sum (+ sum (first lst))]
                 [sum (if (= sum 150) (+ sum 100) sum)]
               (recur (rest lst) sum))))))) 

संपादित करें: जूल्स बताते हैं कि reduceक्लोजर में जल्दी निकलने का समर्थन करते हैं । इसका उपयोग करना अधिक सुरुचिपूर्ण है:

(defn doSomeCalc [lst]  
  (reduce (fn [sum val]
    (if (= sum 10) (reduced sum)
        (let [sum (+ sum val)]
             [sum (if (= sum 150) (+ sum 100) sum)]
           sum))
   lst)))

किसी भी मामले में, आप कार्यात्मक भाषाओं में कुछ भी कर सकते हैं जैसा कि आप अनिवार्य भाषाओं में कर सकते हैं, लेकिन आपको एक सुरुचिपूर्ण समाधान खोजने के लिए अक्सर अपनी मानसिकता को बदलना होगा। अनिवार्य कोडिंग में आप कदम से एक सूची कदम के प्रसंस्करण के बारे में सोचते हैं, जबकि कार्यात्मक भाषाओं में आप सूची में सभी तत्वों को लागू करने के लिए एक ऑपरेशन की तलाश करते हैं।


संपादित करें देखें जिसे मैंने अभी अपने उत्तर में जोड़ा है: क्लोजर का reduceऑपरेशन जल्दी निकलने का समर्थन करता है।
जूल्स

@ जूल्स: कूल - शायद यह एक अधिक मुहावरेदार समाधान है।
जैक्सबी

गलत - या takeWhile'आम ऑपरेशन' नहीं है?
जोनाथन कास्ट

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

क्लोजर take-whileमें @ जूल्स एक आलसी अनुक्रम (डॉक्स के अनुसार) पैदा करता है। इससे निपटने का एक और तरीका ट्रांसड्यूसर (शायद सबसे अच्छा) होगा।
विल नेस

4

जैसा कि अन्य जवाबों से पता चलता है, क्लोजर में reducedकटौती को जल्द रोकने के लिए है:

(defn some-calc [coll]
  (reduce (fn [answer e]
            (let [answer (+ answer e)]
               (case answer
                 10  (reduced answer)
                 150 (+ answer 100)
                 answer)))
          0 coll))

यह आपकी विशेष स्थिति के लिए सबसे अच्छा समाधान है। आप संयोजन के reducedसाथ बहुत अधिक लाभ प्राप्त कर सकते हैं transduce, जिससे आप ट्रांसड्यूसर का उपयोग कर सकते हैं map, filterआदि। हालांकि यह आपके सामान्य प्रश्न के पूर्ण उत्तर से बहुत दूर है।

पलायन जारी है विराम और वापसी के बयानों का एक सामान्यीकृत संस्करण। वे सीधे कुछ योजनाओं ( call-with-escape-continuation), सामान्य लिस्प ( block+ return, catch+ throw) और यहां तक ​​कि सी ( setjmp+ longjmp) में लागू होते हैं। मानक योजना में पाए जाने वाले या हास्केल और स्काला में निरंतरता के रूप में अधिक सामान्य सीमांकित या बिना सीमांकित निरंतरता का उपयोग पलायन निरंतरताओं के रूप में भी किया जा सकता है।

उदाहरण के लिए, रैकेट में आप let/ecइस तरह का उपयोग कर सकते हैं :

(define (some-calc ls)
  (let/ec break ; let break be an escape continuation
    (foldl (lambda (answer e)
             (let ([answer (+ answer e)])
               (case answer
                 [(10)  (break answer)] ; return answer immediately
                 [(150) (+ answer 100)]
                 [else  answer])))
           0 ls)))

कई अन्य भाषाओं में भी अपवाद से निपटने के रूप में निरंतरता-जैसे निर्माण होते हैं। हास्केल में आप विभिन्न त्रुटि साधुओं में से एक का भी उपयोग कर सकते हैं foldM। क्योंकि वे मुख्य रूप से शुरुआती समय के लिए अपवादों या त्रुटि मठों का उपयोग करके निर्माण से निपटने में त्रुटि करते हैं, आमतौर पर सांस्कृतिक रूप से अस्वीकार्य और संभवतः काफी धीमा होता है।

आप कॉल करने के लिए उच्च-क्रम वाले कार्यों से भी नीचे जा सकते हैं।

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

(defn some-calc [coll]
  (loop [answer 0, [e es :as coll] coll]
    (if (empty? coll)
      answer
      (let [answer (+ answer e)]
        (case answer
          10 answer
          150 (recur (+ answer 100) es)
          (recur answer es))))))

1
हास्केल में त्रुटि मानदण्डों का उपयोग करते हुए, मुझे विश्वास नहीं होता कि यहाँ कोई वास्तविक प्रदर्शन दंड है। वे अपवाद से निपटने की रेखाओं के बारे में सोचते हैं, लेकिन वे उसी तरह से काम नहीं करते हैं और किसी भी तरह की स्टैकिंग की आवश्यकता नहीं होती है, इसलिए इस तरह से उपयोग किए जाने पर वास्तव में समस्या नहीं होनी चाहिए। इसके अलावा, यहां तक ​​कि अगर कुछ का उपयोग न करने का एक सांस्कृतिक कारण है MonadError, तो मूल रूप से समतुल्य Eitherकेवल त्रुटि से निपटने के प्रति ऐसा कोई पूर्वाग्रह नहीं है, इसलिए आसानी से विकल्प के रूप में उपयोग किया जा सकता है।
जूल्स

@ जूल्स मुझे लगता है कि लेफ्ट लौटने से गुना को पूरी सूची (या अन्य अनुक्रम) पर जाने से नहीं रोका जा सकता है। हालांकि हास्केल मानक पुस्तकालय इंटर्न के साथ आंतरिक रूप से परिचित नहीं हैं।
निलेर्न

2

जटिल भाग लूप है। हमें इसके साथ शुरू करते हैं। एक लूप को आमतौर पर एकल फ़ंक्शन के साथ पुनरावृत्ति व्यक्त करके कार्यात्मक शैली में बदल दिया जाता है। एक पुनरावृति लूप वेरिएबल का रूपांतरण है।

यहाँ एक सामान्य लूप का कार्यात्मक कार्यान्वयन है:

loop : v -> (v -> v) -> (v -> Bool) -> v
loop init iter cond_to_cont = 
    if cond_to_cont init 
        then loop (iter init) iter cond
        else init

यह लूप वेरिएबल का एक प्रारंभिक मूल्य लेता है, जो फ़ंक्शन एक एकल पुनरावृत्ति [लूप वेरिएबल पर]] (लूप को जारी रखने के लिए एक शर्त) को व्यक्त करता है।

आपका उदाहरण एक सरणी पर एक लूप का उपयोग करता है, जो भी टूट जाता है। अपनी अनिवार्य भाषा में इस क्षमता को भाषा में ही पकाया जाता है। कार्यात्मक प्रोग्रामिंग में ऐसी क्षमता आमतौर पर पुस्तकालय स्तर पर लागू की जाती है। यहाँ एक संभावित कार्यान्वयन है

module Array (foldlc) where

foldlc : v -> (v -> e -> v) -> (v -> Bool) -> Array e -> v
foldlc init iter cond_to_cont arr = 
    loop 
        (init, 0)
        (λ (val, next_pos) -> (iter val (at next_pos arr), next_pos + 1))
        (λ (val, next_pos) -> and (cond_to_cont val) (next_pos < size arr))

में इस :

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

सामान्य लूप की तुलना में पुनरावृत्ति फ़ंक्शन थोड़ा अधिक जटिल है, यह संस्करण सरणी के वर्तमान तत्व का उपयोग करना संभव बनाता है। [यह करी रूप में है।]

ऐसे कार्यों को आमतौर पर "गुना" नाम दिया गया है।

मैंने यह इंगित करने के लिए नाम में "एल" डाला कि सरणी के तत्वों का संचय बाएं-साहचर्य तरीके से किया जाता है; कम से उच्च सूचकांक के लिए एक सरणी पुनरावृति करने के लिए अनिवार्य प्रोग्रामिंग भाषाओं की आदत की नकल करना।

मैंने यह संकेत करने के लिए नाम में "c" डाला कि गुना का यह संस्करण एक ऐसी स्थिति लेता है जो नियंत्रण करता है कि क्या और कब लूप को जल्दी रोका जाए।

बेशक इस तरह के उपयोगिता कार्यों को बेस लाइब्रेरी में आसानी से उपलब्ध कार्यात्मक प्रोग्रामिंग भाषा के साथ उपलब्ध होने की संभावना है। मैंने उन्हें यहाँ प्रदर्शन के लिए लिखा था।

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

आपके लूप में चर एक जोड़ी है ('उत्तर', एक बूलियन जो कि जारी रखने के लिए एन्कोड करता है)।

iter : (Int, Bool) -> Int -> (Int, Bool)
iter (answer, cont) collection_element = 
  let new_answer = answer + collection_element
  in case new_answer of
    10 -> (new_answer, false)
    150 -> (new_answer + 100, true)
    _ -> (new_answer, true)

ध्यान दें कि मैंने एक नया "वैरिएबल" 'new_answer' प्रयोग किया है। ऐसा इसलिए है क्योंकि कार्यात्मक प्रोग्रामिंग में मैं पहले से ही आरंभिक "चर" के मूल्य को नहीं बदल सकता। मुझे प्रदर्शन की चिंता नहीं है, कंपाइलर को जीवन-काल विश्लेषण के माध्यम से 'new_answer' के लिए 'जवाब' की स्मृति का पुन: उपयोग करने के लिए मिल सकता है, अगर यह सोचता है कि यह अधिक कुशल है।

पहले विकसित हमारे लूप फ़ंक्शन में इसे शामिल करना:

doSomeCalc :: Array Int -> Int
doSomeCalc arr = fst (Array.foldlc (0, true) iter snd arr)

"एरे" यहाँ मॉड्यूल नाम है जो फ़ंक्शन फोल्डक निर्यात करता है।

"मुट्ठी", "दूसरा" उन कार्यों के लिए खड़ा है जो अपने जोड़ी पैरामीटर के पहले, दूसरे घटक को वापस करते हैं

fst : (x, y) -> x
snd : (x, y) -> y

इस मामले में "बिंदु मुक्त" शैली doSomeCalc के कार्यान्वयन की पठनीयता बढ़ाती है:

doSomeCalc = Array.foldlc (0, true) iter snd >>> fst

(>>>) फ़ंक्शन रचना है: (>>>) : (a -> b) -> (b -> c) -> (a -> c)

यह ऊपर के समान है, बस "अरेस्ट" पैरामीटर को परिभाषित समीकरण के दोनों किनारों से छोड़ दिया जाता है।

एक अंतिम बात: मामले की जाँच (सरणी == अशक्त)। बेहतर डिज़ाइन की गई प्रोग्रामिंग भाषाओं में, लेकिन यहां तक ​​कि कुछ बुनियादी अनुशासन वाली बुरी तरह से डिज़ाइन की गई भाषाओं में, बल्कि गैर-अस्तित्व को व्यक्त करने के लिए एक वैकल्पिक प्रकार का उपयोग करता है। कार्यात्मक प्रोग्रामिंग के साथ ऐसा करने के लिए बहुत कुछ नहीं है, जो सवाल अंततः के बारे में है, इस प्रकार मैं इसके साथ सौदा नहीं करता हूं।


0

सबसे पहले, लूप को थोड़ा फिर से लिखें, जैसे कि लूप का प्रत्येक पुनरावृत्ति या तो जल्दी बाहर निकलता है, या answerठीक एक बार म्यूट करता है:

    public int doSomeCalc(int[] array)
    {
        int answer = 0;
        if(array!=null)
        {
            for(int e: array)
            {
                if(answer + e == 10) return answer + e;
                else if(answer + e == 150) answer = answer + e + 100;
                else answer = answer + e;
            }
        }
        return answer;
    }

यह स्पष्ट होना चाहिए कि इस संस्करण का व्यवहार पहले की तरह ही है, लेकिन अब, यह पुनरावृत्ति शैली में बदलने के लिए बहुत अधिक सीधा है। यहाँ एक सीधा Haskell अनुवाद है:

doSomeCalc :: [Int] -> Int
doSomeCalc = recurse 0
  where recurse :: Int -> [Int] -> Int
        recurse answer [] = answer
        recurse answer (e:array)
          | answer + e == 10 = answer + e
          | answer + e == 150 = recurse (answer + e + 100) array
          | otherwise = recurse (answer + e) array

अब यह विशुद्ध रूप से कार्यात्मक है, लेकिन हम इसे स्पष्ट दक्षता के बजाय गुना का उपयोग करके दक्षता और पठनीयता दोनों दृष्टिकोण से बेहतर बना सकते हैं:

import Control.Monad (foldM)

doSomeCalc :: [Int] -> Int
doSomeCalc = either id id . foldM go 0
  where go :: Int -> Int -> Either Int Int
        go answer e
          | answer + e == 10 = Left (answer + e)
          | answer + e == 150 = Right (answer + e + 100)
          | otherwise = Right (answer + e)

इस संदर्भ में, Leftइसके मूल्य के साथ जल्दी-बाहर निकलता है, और Rightइसके मूल्य के साथ पुनरावृत्ति जारी रखता है।


इसे अब थोड़ा और सरल बनाया जा सकता है, जैसे:

import Control.Monad (foldM)

doSomeCalc :: [Int] -> Int
doSomeCalc = either id id . foldM go 0
  where go :: Int -> Int -> Either Int Int
        go answer e
          | answer' == 10 = Left 10
          | answer' == 150 = Right 250
          | otherwise = Right answer'
          where answer' = answer + e

यह अंतिम हास्केल कोड के रूप में बेहतर है, लेकिन यह अब थोड़ा कम स्पष्ट है कि यह मूल जावा में वापस कैसे आता है।

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