कैसे परिवर्तनशील चर का उपयोग किए बिना उपयोगी जावा प्रोग्राम लिखने के लिए


12

मैं कार्यात्मक प्रोग्रामिंग के बारे में एक लेख पढ़ रहा था जहां लेखक कहता है

(take 25 (squares-of (integers)))

ध्यान दें कि इसका कोई चर नहीं है। वास्तव में, यह तीन कार्यों और एक स्थिर से अधिक कुछ नहीं है। एक चर का उपयोग किए बिना जावा में पूर्णांकों के वर्गों को लिखने का प्रयास करें। ओह, शायद ऐसा करने का एक तरीका है, लेकिन यह निश्चित रूप से स्वाभाविक नहीं है, और यह मेरे कार्यक्रम के रूप में अच्छी तरह से नहीं पढ़ेगा।

क्या जावा में इसे हासिल करना संभव है? मान लें कि आपको पहले 15 पूर्णांक के वर्ग को प्रिंट करना आवश्यक है, तो क्या आप चर का उपयोग किए बिना एक या उसके लिए लूप लिख सकते हैं?

मॉड नोटिस

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


19
आपका कार्यात्मक उदाहरण अंदर पर चर का उपयोग करता है, लेकिन भाषा पर्दे के पीछे यह सब करती है। आपने अप्रिय भागों को प्रभावी ढंग से किसी ऐसे व्यक्ति को सौंप दिया है, जिस पर आपको विश्वास है कि उसने इसे सही तरीके से किया है।
ब्लर एफएल

12
@ ब्लरफ्ल: "पर्दे के पीछे" तर्क सभी भाषा-आधारित बहस को मारता है, क्योंकि कोड के हर टुकड़े का अंततः x86 मशीन कोड में अनुवाद किया जाता है। x86 कोड ऑब्जेक्ट-ओरिएंटेड नहीं है, प्रक्रियात्मक नहीं है, कार्यात्मक नहीं है, कुछ भी नहीं है, लेकिन ये श्रेणियां प्रोग्रामिंग भाषाओं के लिए मूल्यवान टैग हैं। भाषा को देखें, क्रियान्वयन को नहीं।
14

10
@thiton असहमत। ब्लरफ्ल क्या कह रहा है कि वे फ़ंक्शन संभवतः उसी प्रोग्रामिंग भाषा में लिखे गए चर का उपयोग करते हैं । यहां निम्न-स्तर पर जाने की आवश्यकता नहीं है। स्निपेट केवल पुस्तकालय कार्यों का उपयोग कर रहा है। आप आसानी से जावा में एक ही कोड लिख सकते हैं, देखें: squaresOf(integers()).take(25)(उन कार्यों को लिखना पाठक के लिए एक अभ्यास के रूप में छोड़ दिया जाता है; कठिनाई के लिए अनंत सेट में निहित है integers(), लेकिन यह जावा के लिए एक समस्या है क्योंकि इसके उत्सुक मूल्यांकन के साथ कुछ भी नहीं करना है। चर)
एंड्रेस एफ।

6
वह बोली भ्रामक और भ्रामक है, वहाँ कोई जादू नहीं है, बस सिंटैक्टिक चीनी है
यानि

2
@thiton मैं सुझाव देता हूं कि आप FP भाषाओं के बारे में अधिक जानें, लेकिन फिर भी, कोड स्निपेट इस दावे का समर्थन नहीं करता (या अस्वीकार) कि "चर" की आवश्यकता नहीं है (जिसके द्वारा मुझे लगता है कि आपका मतलब "परिवर्तनशील चर" है, क्योंकि अन्य तरह एफपी में आम है)। स्निपेट केवल पुस्तकालय कार्यों को दिखाता है जो जावा में लागू किया जा सकता था, साथ ही आलसी / उत्सुक समस्याओं को रोकते हुए जो यहां ऑफोप्टिक हैं।
एंड्रेस एफ।

जवाबों:


31

क्या विनाशकारी अपडेट का उपयोग किए बिना जावा में इस तरह के उदाहरण को लागू करना संभव है? हाँ। हालाँकि, @Thiton और लेख के अनुसार, यह बदसूरत होगा (किसी के स्वाद पर निर्भर करता है)। एक तरीका पुनरावृत्ति का उपयोग कर रहा है; यहाँ एक हास्केल उदाहरण है जो कुछ ऐसा ही करता है:

unfoldr      :: (b -> Maybe (a, b)) -> b -> [a]
unfoldr f b  =
  case f b of
   Just (a,new_b) -> a : unfoldr f new_b
   Nothing        -> []  

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


मैंने लेख को असंतोषजनक पाया और कुछ अतिरिक्त बिंदु बनाना चाहूंगा:

यह लेख कार्यात्मक प्रोग्रामिंग और इसके लाभों का एक बहुत ही खराब और भ्रमित करने वाला विवरण है। मैं कार्यात्मक प्रोग्रामिंग के बारे में सीखने के लिए अन्य स्रोतों की जोरदार सिफारिश करूंगा ।

लेख के बारे में सबसे भ्रमित करने वाला हिस्सा यह है कि यह उल्लेख नहीं करता है कि जावा में असाइनमेंट स्टेटमेंट के लिए दो उपयोग हैं (और अधिकांश अन्य मुख्यधारा की भाषाएँ):

  1. एक नाम के लिए एक मूल्य बांधना: final int MAX_SIZE = 100;

  2. विनाशकारी अद्यतन: int a = 3; a += 1; a++;

फ़ंक्शनल प्रोग्रामिंग दूसरे को बचाती है, लेकिन पहले को गले लगाती है (उदाहरण: let-expressions, फ़ंक्शन पैरामीटर्स, टॉप-लेवल defineइटियन) । क्योंकि अन्यथा लेख सिर्फ मूर्ख लगता है और आप सोच छोड़ सकता है, क्या कर रहे हैं यह समझ के लिए एक बहुत ही महत्वपूर्ण मुद्दा है take, squares-ofऔर integersनहीं तो चर?

इसके अलावा, उदाहरण अर्थहीन है। यह , या take, के कार्यान्वयन को नहीं दिखाता है । हम सभी जानते हैं कि, वे परिवर्तनशील चर का उपयोग करके कार्यान्वित किए जाते हैं। जैसा कि @ मार्टिन ने कहा, आप जावा में इस उदाहरण को तुच्छ रूप से लिख सकते हैं।squares-ofintegers

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

यह कागज़ गैर-क्रियात्मक प्रोग्रामर के बड़े समुदाय को प्रदर्शित करने का एक प्रयास है, जो फंक्शनल प्रोग्रामिंग के सिनी ce कैन्स, और फंक्शनल प्रोग्रामर्स को इसके फायदों का पूरा फायदा उठाने के लिए इसके फायदों का फायदा उठाने में मदद करता है।

[...]

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


10
+1 के लिए "मैं इस लेख और इसके जैसे अन्य लोगों से बचने की सलाह दूंगा यदि आप वास्तव में कार्यात्मक प्रोग्रामिंग के बारे में सीखना चाहते हैं। ऐसा लगता है कि अवधारणाओं और बुनियादी बातों को पढ़ाने के बजाय चौंकाने वाला और अपमानजनक होने के लक्ष्य से अधिक लिखा गया है।"

3
आधे लोग एफपी न करने का कारण यह है कि वे uni में इसके बारे में कुछ भी नहीं सुनते / सीखते हैं, और दूसरा आधा इसलिए है क्योंकि जब वे इस पर गौर करते हैं तो उन्हें ऐसे लेख मिलते हैं जो उन दोनों को छोड़ देते हैं और यह सोचते हुए कि यह कुछ काल्पनिक है लाभों के बारे में सोचा-समझा तर्क होने के बजाय खेलना। सूचना के बेहतर स्रोत देने के लिए +1
जिमी होफा

3
प्रश्न के उत्तर को अपने शीर्ष पर रखें अगर आप ऐसा करेंगे तो यह प्रश्न अधिक प्रत्यक्ष होगा और शायद यह प्रश्न तब भी खुला रहेगा (प्रत्यक्ष प्रश्न-केंद्रित उत्तर के साथ)
जिमी होफा

2
नाइटपिक के लिए क्षमा करें, लेकिन मुझे यह समझ में नहीं आता है कि आपने यह हैसेल कोड क्यों चुना है। मैंने LYAH पढ़ा है और आपका उदाहरण मेरे लिए मुश्किल है। मैं मूल प्रश्न से संबंध भी नहीं देखता। आपने केवल take 25 (map (^2) [1..])एक उदाहरण के रूप में उपयोग क्यों नहीं किया ?
डैनियल कपलान

2
@tieTYT अच्छा सवाल - इस ओर इशारा करने के लिए धन्यवाद। मैंने उस उदाहरण का उपयोग इसलिए किया क्योंकि यह दर्शाता है कि कैसे पुनरावर्तन का उपयोग करके और परिवर्तनशील चर से बचने के लिए संख्याओं की एक सूची तैयार की जाती है। मेरा इरादा ओपी के लिए उस कोड को देखने और जावा में कुछ इसी तरह करने के बारे में सोचना था। अपने कोड स्निपेट को संबोधित करने के लिए, क्या है [1..]? यह हास्केल में निर्मित एक शांत विशेषता है, लेकिन ऐसी सूची बनाने के पीछे की अवधारणाओं को स्पष्ट नहीं करता है। मुझे यकीन है कि Enumकक्षा के उदाहरण (जिसे सिंटैक्स की आवश्यकता होती है) भी सहायक होते हैं, लेकिन उन्हें खोजने के लिए बहुत आलसी थे। इस प्रकार, unfoldr। :)

27

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

तो, जावा तरीका होगा:

for (int i = 1; i <= 25; ++i) {
    System.out.println(i*i);
}

और चर की घृणा के कारण अपने आप को किसी भी अधिक जटिल तरीके से लिखने के लिए मूर्ख मत बनने दो।


5
"चरों से घृणा"? Ooookay ... आपने कार्यात्मक प्रोग्रामिंग के बारे में क्या पढ़ा है? आपने किन भाषाओं की कोशिश की है? कौन सा ट्यूटोरियल?
एंड्रेस एफ।

8
@AndresF .: हास्केल में कोर्सवर्क के दो साल से ज्यादा। मैं नहीं कहता कि एफपी खराब है। हालाँकि, कई FP-vs-IP चर्चाओं (जैसे लिंक किए गए लेख) में एक प्रवृत्ति है कि पुनः असाइन किए गए नामित संस्थाओं (AKA चर) के उपयोग की निंदा करने के लिए, और अच्छे कारण या डेटा के बिना निंदा करने के लिए। मेरी पुस्तक में अनुचित निंदा घृणा है। और घृणा वास्तव में बुरे कोड के लिए बनाती है।
thiton

10
"वेरिएबल्स से घृणा" कारणगत ओवरसाइम्पलाइज़ेशन en.wikipedia.org/wiki/Fallacy_of_the_single_cause स्टेटलेस प्रोग्रामिंग के कई लाभ हैं जो कि जावा में भी हो सकते हैं, हालांकि मैं आपके जवाब से सहमत हूं कि जावा में लागत जटिलता में बहुत अधिक होगी। कार्यक्रम और गैर मुहावरेदार होना। मैं अभी भी इस विचार को दूर करने के लिए हाथ नहीं हिलाता हूं कि स्टेटलेस प्रोग्रामिंग अच्छी है और स्टेटफुल खराब है क्योंकि एक तर्क के बजाय कुछ भावनात्मक प्रतिक्रिया है, अच्छी तरह से सोचा गया कि लोग अनुभव के कारण आए हैं।
जिमी होफा

2
@JimmyHoffa क्या कहती है, इनलाइन मैं आपको जॉन कार्मैक को अनिवार्य भाषाओं में कार्यात्मक-शैली प्रोग्रामिंग के विषय पर संदर्भित करूंगा (उनके मामले में C ++) ( altdevblogaday.com/2012/04/26/functional -programming-in-c )।
स्टीवन एवर्स

5
अनुचित निंदा घृणा नहीं है, और उत्परिवर्तनीय स्थिति से बचना अनुचित नहीं है।
माइकल शॉ

21

पुनरावृत्ति के साथ सबसे सरल मैं एक पैरामीटर के साथ एक फ़ंक्शन है। यह बहुत जावा-एस्के नहीं है, लेकिन यह काम करता है:

public class squares
{
    public static void main(String[] args)
    {
        squares(15);
    }

    private static void squares(int x)
    {
        if (x>0)
        {
            System.out.println(x*x);
            squares(x-1);
        }
    }
}

3
वास्तव में जावा उदाहरण के साथ प्रश्न का उत्तर देने के प्रयास के लिए +1।
KChaloux

मैं इसे कोड गोल्फ स्टाइल प्रेजेंटेशन ( मॉड नोटिस देखें ) के लिए डाउनवोट करूंगा, लेकिन अपने आप को एरो दबाने के लिए मजबूर नहीं कर सकता क्योंकि यह कोड मेरे पसंदीदा उत्तर में दिए गए बयानों से पूरी तरह मेल खाता है : "1) म्यूटेशन की कमी, 2) का उपयोग पुनरावृत्ति, और 3) छोरों की कमी "
gnat

3
@gnat: यह उत्तर मॉड नोटिस से पहले पोस्ट किया गया था। मैं महान शैली के लिए नहीं जा रहा था, मैं सरलता के लिए जा रहा था, और ओपी के मूल प्रश्न को संतुष्ट कर रहा था; वर्णन करने के लिए है कि यह है जावा में ऐसी बातें करना संभव।
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner यकीन है कि; यह मुझे DVing से नहीं रोक सकता (क्योंकि आप अनुपालन करने के लिए संपादित करने में सक्षम होने वाले हैं) - यह आश्चर्यजनक रूप से एकदम सही मिलान है जिसने जादू किया। अच्छी तरह से किया, वास्तव में अच्छी तरह से किया, काफी शिक्षाप्रद - धन्यवाद
gnat

16

आपके कार्यात्मक उदाहरण में हम यह नहीं देखते कि कार्यों squares-ofऔर takeकार्यों को कैसे लागू किया जाता है। मैं एक जावा विशेषज्ञ नहीं हूं, लेकिन मुझे पूरा यकीन है कि हम इस तरह के बयान को सक्षम करने के लिए उन कार्यों को लिख सकते हैं ...

squares_of(integers).take(25);

जो इतना अलग नहीं है।


6
नाइटपिक: squares-ofजावा में एक मान्य नाम नहीं है ( squares_ofहालांकि है)। लेकिन अन्यथा, अच्छा बिंदु जो दर्शाता है कि लेख का उदाहरण खराब है।

मुझे संदेह है कि लेख का integerआलसी पूर्णांक उत्पन्न करता है, और takeफ़ंक्शन 25 squared-ofसंख्याओं से चुनता है integer। संक्षेप में, आपके पास integerअनंत तक पूर्णांकों का निर्माण करने के लिए एक फ़ंक्शन होना चाहिए ।
ओनेसिमसबाउंडर

(integer)एक फ़ंक्शन की तरह कुछ को कॉल करना थोड़ा पागलपन है - एक फ़ंक्शन अभी भी कुछ ऐसा है जो एक तर्क को एक मूल्य पर मैप करता है । यह पता चला है कि (integer)एक फ़ंक्शन नहीं है, लेकिन केवल एक मूल्य है। यहां तक ​​कि integerयह भी कहा जा सकता है कि यह एक ऐसा चर है जो संख्याओं के अनंत रूप से जुड़ा हुआ है।
ingo

6

जावा में आप पुनरावृत्तियों के साथ यह (esp। अनंत सूची भाग) कर सकते हैं। निम्नलिखित कोड नमूने में, Takeनिर्माणकर्ता को आपूर्ति की गई संख्या मनमाने ढंग से अधिक हो सकती है।

class Example {
    public static void main(String[] a) {
        Numbers test = new Take(25, new SquaresOf(new Integers()));
        while (test.hasNext())
            System.out.println(test.next());
    }
}

या श्रृंखला योग्य कारखाने के तरीकों के साथ:

class Example {
    public static void main(String[] a) {
        Numbers test = Numbers.integers().squares().take(23);
        while (test.hasNext())
            System.out.println(test.next());
    }
}

जहां SquaresOf, Takeऔर Integersविस्तारNumbers

abstract class Numbers implements Iterator<Integer> {
    public static Numbers integers() {
        return new Integers();
    }

    public Numbers squares() {
        return new SquaresOf(this);
    }

    public Numbers take(int c) {
        return new Take(c, this);
    }
    public void remove() {}
}

1
यह कार्यात्मक एक पर OO प्रतिमान की श्रेष्ठता को दर्शाता है; उचित OO डिज़ाइन के साथ आप कार्यात्मक प्रतिमान की नकल कर सकते हैं, लेकिन आप एक कार्यात्मक शैली में OO प्रतिमान की नकल नहीं कर सकते।
m3th0dman

3
@ m3th0dman: उचित OO डिज़ाइन के साथ, आप संभवतः किसी भी भाषा की तरह अर्ध-अर्ध-नक़ली नक़ल कर सकते हैं, जिसमें तार, सूचियाँ और / या शब्दकोश अर्ध-assedly नक़ल OO हो सकते हैं। सामान्य-उद्देश्य वाली भाषाओं की ट्यूरिंग तुल्यता का अर्थ है कि पर्याप्त प्रयास, किसी भी भाषा में किसी भी अन्य की विशेषताओं का अनुकरण किया जा सकता है।
cHao

ध्यान दें कि जावा-शैली के पुनरावृत्तियों जैसे कि while (test.hasNext()) System.out.println(test.next())एफपी में नंबर-नो होगा; पुनरावृत्तियाँ स्वाभाविक रूप से परिवर्तनशील हैं।
cHao

1
@ कोहाओ मैं शायद ही मानता हूं कि सच्चे इनकैप्सुलेशन या बहुरूपता की नकल की जा सकती है; जावा (इस उदाहरण में) वास्तव में सख्त उत्सुक मूल्यांकन के कारण एक कार्यात्मक भाषा की नकल नहीं कर सकता है। मेरा यह भी मानना ​​है कि पुनरावृत्तियों में पुनरावृत्तियों को लिखा जा सकता है।
m3th0dman

@ m3th0dman: बहुरूपता की नकल करना बिल्कुल भी मुश्किल नहीं होगा; यहां तक ​​कि सी और असेंबली भाषा भी कर सकती है। बस विधि को ऑब्जेक्ट या फ़ील्ड डिस्क्रिप्टर / वाइबेट में फ़ील्ड बनाएं। और डेटा छिपाने की भावना में इनकैप्सुलेशन की सख्त आवश्यकता नहीं है; बाहर की आधी भाषाएं इसे प्रदान नहीं करती हैं, जब आपकी वस्तु अपरिवर्तनीय होती है, तो इससे कोई फर्क नहीं पड़ता कि लोग किसी भी तरह इसकी हिम्मत देख सकते हैं। जरूरत है कि सभी डेटा रैपिंग है , जो उपर्युक्त विधि-क्षेत्र आसानी से प्रदान कर सकते हैं।
cHao

6

लघु संस्करण:

जावा में एकल-कार्य शैली को मज़बूती से बनाने के लिए, आपको (1) टेल-कॉल एलिमिनेशन के लिए किसी तरह के अपरिवर्तनीय-अनुकूल बुनियादी ढाँचे, और (2) संकलक- या रनटाइम स्तर के समर्थन की आवश्यकता होगी।

हम बहुत से बुनियादी ढांचे को लिख सकते हैं, और हम स्टैक को भरने से बचने की कोशिश करने के लिए चीजों को व्यवस्थित कर सकते हैं। लेकिन जब तक प्रत्येक कॉल एक स्टैक फ्रेम लेता है, तब तक एक सीमा होगी कि आप कितना पुनरावृत्ति कर सकते हैं। अपने iterables को छोटा और / या आलसी रखें, और आपके पास प्रमुख मुद्दे नहीं होने चाहिए। कम से कम आप जिन समस्याओं में भाग लेंगे उनमें से एक बार में एक लाख परिणाम वापस करने की आवश्यकता नहीं है। :)

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


दीर्घ संस्करण:

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

जावा को शुरू से ही एक अनिवार्य , वस्तु-उन्मुख भाषा के रूप में डिजाइन किया गया था ।

  • शाही भाषाएँ लगभग हमेशा किसी न किसी प्रकार के परिवर्तनशील चर पर निर्भर करती हैं। वे पुनरावृत्ति पर पुनरावृत्ति का पक्ष लेते हैं, उदाहरण के लिए, और लगभग सभी पुनरावृत्त निर्माण - यहां तक ​​कि while (true)और for (;;)! - पूरी तरह से परिवर्तन से पुनरावृत्ति के लिए कहीं चर पर निर्भर हैं।
  • ऑब्जेक्ट-ओरिएंटेड भाषाएं प्रत्येक प्रोग्राम को एक-दूसरे को संदेश भेजने वाली वस्तुओं के एक ग्राफ के रूप में और लगभग सभी मामलों में, कुछ संदेशों को म्यूट करके उन संदेशों का जवाब देते हुए बहुत अधिक कल्पना करती हैं।

उन डिज़ाइन निर्णयों का अंतिम परिणाम यह है कि बिना परिवर्तनशील चर के, जावा के पास किसी भी चीज़ की स्थिति को बदलने का कोई तरीका नहीं है - यहां तक ​​कि "हैलो वर्ल्ड!" स्क्रीन में एक आउटपुट स्ट्रीम शामिल है, जिसमें एक म्यूटेबल बफर में बाइट्स चिपके हुए हैं ।

इसलिए, सभी व्यावहारिक उद्देश्यों के लिए, हम अपने स्वयं के कोड से वैरिएबल को सीमित करने के लिए सीमित हैं । ठीक है, हम थोड़े कर सकते हैं। लगभग। मूल रूप से हमें जो चाहिए वह लगभग सभी पुनरावृत्ति को पुनर्संरचना के साथ बदलना होगा, और पुनरावर्ती कॉल के साथ सभी उत्परिवर्तन बदले हुए मूल्य को वापस करना होगा। इस तरह...

class Ints {
     final int value;
     final Ints tail;

     public Ints(int value, Ints rest) {
         this.value = value;
         this.tail = rest;
     }
     public Ints next() { return this.tail; }
     public int value() { return this.value; }
}

public Ints take(int count, Ints input) {
    if (count == 0 || input == null) return null;
    return new Ints(input.value(), take(count - 1, input.next()));
}    

public Ints squares_of(Ints input) {
    if (input == null) return null;
    int i = input.value();
    return new Ints(i * i, squares_of(input.next()));
}

मूल रूप से, हम एक लिंक की गई सूची बनाते हैं, जहां प्रत्येक नोड अपने आप में एक सूची है। प्रत्येक सूची में एक "सिर" (वर्तमान मूल्य) और एक "पूंछ" (शेष सबलिस्ट) है। अधिकांश कार्यात्मक भाषाएँ इसके लिए कुछ महत्वपूर्ण हैं, क्योंकि यह कुशल अपरिवर्तनीयता के लिए बहुत ही अनुकूलनीय है। एक "अगला" ऑपरेशन केवल पूंछ लौटाता है, जो आमतौर पर पुनरावर्ती कॉल के ढेर में अगले स्तर तक पहुंच जाता है।

अब, यह इस सामान का एक अत्यंत उपयोगी संस्करण है। लेकिन जावा में इस दृष्टिकोण के साथ एक गंभीर समस्या का प्रदर्शन करना काफी अच्छा है। इस कोड पर विचार करें:

public function doStuff() {
    final Ints integers = ...somehow assemble list of 20 million ints...;
    final Ints result = take(25, squares_of(integers));
    ...
}

हालाँकि हमें परिणाम के लिए केवल 25 इंच की आवश्यकता है, लेकिन squares_ofयह नहीं पता है। यह हर संख्या के वर्ग को वापस करने जा रहा है integers। रिकर्सियन 20 मिलियन का स्तर जावा में बहुत बड़ी समस्याओं का कारण बनता है।

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

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

एक अर्ध-कसरत ... आलसी मूल्यांकन। हमारे पास अभी भी स्टैक सीमाएं हैं, लेकिन वे उन कारकों से बंधे हो सकते हैं जिन पर हमारा अधिक नियंत्रण है। हमें 25 लाख रिटर्न की गणना करने की जरूरत नहीं है। :)

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

// Represents something that can give us instances of OutType.
// We can basically treat this class like a list.
interface Source<OutType> {
     public Source<OutType> next();
     public OutType value();
}

// Represents an operation that turns an InType into an OutType.
// Note, these can be the same type.  We're just flexible like that.
interface Transform<InType, OutType> {
    public OutType appliedTo(InType input);
}

// Represents an action (as opposed to a function) that can run on
// every element of a sequence.
abstract class Action<InType> {
    abstract void doWith(final InType input);
    public void doWithEach(final Source<InType> input) {
        if (input == null) return;
        doWith(input.value());
        doWithEach(input.next());
    }
}

// A list of Integers.
class Ints implements Source<Integer> {
     final Integer value;
     final Ints tail;
     public Ints(Integer value, Ints rest) {
         this.value = value;
         this.tail = rest;
     }
     public Ints(Source<Integer> input) {
         this.value = input.value();
         this.tail = new Ints(input.next());
     }
     public Source<Integer> next() { return this.tail; }
     public Integer value() { return this.value; }
     public static Ints fromArray(Integer[] input) {
         return fromArray(input, 0, input.length);
     }
     public static Ints fromArray(Integer[] input, int start, int end) {
         if (end == start || input == null) return null;
         return new Ints(input[start], fromArray(input, start + 1, end));
     }
}

// An example of the spiff we get by splitting the "iterator" interface
// off.  These ints are effectively generated on the fly, as opposed to
// us having to build a huge list.  This saves huge amounts of memory
// and CPU time, for the rather common case where the whole sequence
// isn't needed.
class Range implements Source<Integer> {
    final int start, end;
    public Range(int start, int end) {
        this.start = start;
        this.end = end;
    }
    public Integer value() { return start; }
    public Source<Integer> next() {
        if (start >= end) return null;
        return new Range(start + 1, end);
    }
}

// This takes each InType of a sequence and turns it into an OutType.
// This *takes* a Transform, rather than just *implementing* Transform,
// because the transforms applied are likely to be specified inline.
// If we just let people override `value()`, we wouldn't easily know what type
// to return, and returning our own type would lose the transform method.
static class Mapper<InType, OutType> implements Source<OutType> {
    private final Source<InType> input;
    private final Transform<InType, OutType> transform;

    public Mapper(Transform<InType, OutType> transform, Source<InType> input) {
        this.transform = transform;
        this.input = input;
    }

    public Source<OutType> next() {
         return new Mapper<InType, OutType>(transform, input.next());
    }
    public OutType value() {
         return transform.appliedTo(input.value());
    }
}

// ...

public <T> Source<T> take(int count, Source<T> input) {
    if (count <= 0 || input == null) return null;
    return new Source<T>() {
        public T value() { return input.value(); }
        public Source<T> next() { return take(count - 1, input.next()); }
    };
}

(ध्यान रखें कि यदि यह वास्तव में जावा में व्यवहार्य था, तो कम से कम कुछ हद तक कोड जैसा कि पहले से ही एपीआई का हिस्सा होगा।)

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

public Source<Integer> squares_of(Source<Integer> input) {
     final Transform<Integer, Integer> square = new Transform<Integer, Integer>() {
         public Integer appliedTo(final Integer i) { return i * i; }
     };
     return new Mapper<>(square, input);
}


public void example() {
    final Source<Integer> integers = new Range(0, 1000000000);

    // and, as for the author's "bet you can't do this"...
    final Source<Integer> squares = take(25, squares_of(integers));

    // Just to make sure we got it right :P
    final Action<Integer> printAction = new Action<Integer>() {
        public void doWith(Integer input) { System.out.println(input); }
    };
    printAction.doWithEach(squares);
}

यह ज्यादातर काम करता है, लेकिन यह अभी भी कुछ हद तक ओवरफ्लो होने का खतरा है। प्रयास करें take2 अरब ints ing और उन पर कुछ कार्रवाई कर रही है। : P यह अंततः एक अपवाद फेंक देगा, कम से कम जब तक 64+ GB RAM मानक नहीं बन जाता। समस्या यह है कि एक प्रोग्राम की मेमोरी की मात्रा जो उसके स्टैक के लिए आरक्षित है, वह इतनी बड़ी नहीं है। यह आम तौर पर 1 और 8 MiB के बीच होता है। (आप बड़ी लिए पूछ सकते हैं, लेकिन यह सब इतना आप कितना के लिए पूछना कोई फर्क नहीं पड़ता - आप कॉल take(1000000000, someInfiniteSequence), आप होगा । एक अपवाद मिल) सौभाग्य से, आलसी मूल्यांकन के साथ, कमजोर है एक क्षेत्र में हम बेहतर कर सकते हैं नियंत्रण । हमें बस इस बात से सावधान रहना है कि हम कितना take()

यह अभी भी बहुत सारी समस्याओं को बढ़ाएगा, क्योंकि हमारे ढेर का उपयोग रैखिक रूप से बढ़ जाता है। प्रत्येक कॉल एक तत्व को संभालती है और शेष को किसी अन्य कॉल में पास करती है। अब जब मैं इसके बारे में सोचता हूं, हालांकि, एक चाल है जिसे हम खींच सकते हैं जो हमें काफी अधिक हेडरूम दे सकता है: कॉल की श्रृंखला को कॉल के पेड़ में बदल दें। कुछ इस तरह से विचार करें:

public <T> void doSomethingWith(T input) { /* magic happens here */ }
public <T> Source<T> workWith(Source<T> input, int count) {
    if (count < 0 || input == null) return null;
    if (count == 0) return input;
    if (count == 1) {
        doSomethingWith(input.value());
        return input.next();
    }
    return (workWith(workWith(input, count/2), count - count/2);
}

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

समस्या यह है, यह फ़ंक्शन एक इनपुट चाहता है - और एक लिंक की गई सूची के साथ, लंबाई प्राप्त करने के लिए पूरी सूची का पता लगाने की आवश्यकता होती है। यह आसानी से हल है, यद्यपि; बस परवाह नहीं है कि कितनी प्रविष्टियाँ हैं। :) उपरोक्त कोड Integer.MAX_VALUEगिनती की तरह कुछ के साथ काम करेगा , क्योंकि एक नल वैसे भी प्रसंस्करण बंद कर देता है। गिनती ज्यादातर वहाँ है इसलिए हमारे पास एक ठोस आधार मामला है। यदि आप Integer.MAX_VALUEकिसी सूची में प्रविष्टियों से अधिक होने का अनुमान लगाते हैं , तो आप workWithवापसी मूल्य की जांच कर सकते हैं - यह अंत में शून्य होना चाहिए। अन्यथा, पुनरावृत्ति।

ध्यान रखें, यह आपको बताए गए तत्वों को छूता है। यह आलसी नहीं है; यह अपनी बात तुरंत करता है। आप इसे केवल क्रियाओं के लिए करना चाहते हैं - अर्थात, ऐसी वस्तुएं जिनका एकमात्र उद्देश्य किसी सूची में प्रत्येक तत्व के लिए स्वयं को लागू करना है। जैसा कि मैं इसे अभी सोच रहा हूँ, यह मुझे लगता है कि अगर रेखीय रखा जाता है तो अनुक्रम बहुत कम जटिल होगा; कोई समस्या नहीं होनी चाहिए, क्योंकि अनुक्रम खुद को वैसे भी नहीं बुलाते हैं - वे केवल उन वस्तुओं को बनाते हैं जो उन्हें फिर से कॉल करते हैं।


3

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

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

यह महत्वपूर्ण है जैसे कुछ Seqया Iteratorइंटरफेस का उपयोग करें क्योंकि इसका मतलब है कि सूची आलसी बनाई गई है। Iteratorइंटरफेस, अपरिवर्तनीय वस्तु नहीं किया जा सकता तो कम कार्यात्मक प्रोग्रामिंग के लिए उपयुक्त है - अगर आप नहीं बता सकते हैं एक समारोह में पारित मूल्य यह द्वारा बदल दिया गया है, तो आप कार्यात्मक प्रोग्रामिंग के प्रमुख फायदों में से एक खो देते हैं।

स्पष्ट रूप integersसे सभी पूर्णांकों की एक सूची होनी चाहिए, इसलिए मैंने शून्य पर शुरू किया और वैकल्पिक रूप से सकारात्मक और नकारात्मक लौटा।

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

square ( int x )फ़ंक्शन का बिंदु केवल head()दो बार कॉल करने की आवश्यकता को हटाने के लिए है - आम तौर पर मैंने इसे अंतिम चर में मान डालकर किया होगा, लेकिन इस फ़ंक्शन को जोड़ने का मतलब है कि कार्यक्रम में कोई चर नहीं हैं, केवल फ़ंक्शन पैरामीटर।

इस तरह की प्रोग्रामिंग के लिए जावा के वर्बोसिटी ने मुझे C99 में अपने दुभाषिया के दूसरे संस्करण को लिखने के लिए प्रेरित किया।

public class Squares {
    interface Seq<T> {
        T head();
        Seq<T> tail();
    }

    public static void main (String...args) {
        print ( take (25, integers ) );
        print ( take (25, squaresOf ( integers ) ) );
        print ( take (25, squaresOfUsingMap ( integers ) ) );
    }

    static Seq<Integer> CreateIntSeq ( final int n) {
        return new Seq<Integer> () {
            public Integer head () {
                return n;
            }
            public Seq<Integer> tail () {
                return n > 0 ? CreateIntSeq ( -n ) : CreateIntSeq ( 1 - n );
            }
        };
    }

    public static final Seq<Integer> integers = CreateIntSeq(0);

    public static Seq<Integer> squaresOf ( final Seq<Integer> source ) {
        return new Seq<Integer> () {
            public Integer head () {
                return square ( source.head() );
            }
            public Seq<Integer> tail () {
                return squaresOf ( source.tail() );
            }
        };
    }

    // mapping a function over a list rather than implementing squaring of each element
    interface Fun<T> {
        T apply ( T value );
    }

    public static Seq<Integer> squaresOfUsingMap ( final Seq<Integer> source ) {
        return map ( new Fun<Integer> () {
            public Integer apply ( final Integer value ) {
                return square ( value );
            }
        }, source );
    }

    public static <T> Seq<T> map ( final Fun<T> fun, final Seq<T> source ) {
        return new Seq<T> () {
            public T head () {
                return fun.apply ( source.head() );
            }
            public Seq<T> tail () {
                return map ( fun, source.tail() );
            }
        };
    }

    public static Seq<Integer> take ( final int count,  final Seq<Integer> source ) {
        return new Seq<Integer> () {
            public Integer head () {
                return source.head();
            }
            public Seq<Integer> tail () {
                return count > 0 ? take ( count - 1, source.tail() ) : nil;
            }
        };
    }

    public static int square ( final int x ) {
        return x * x;
    }

    public static final Seq<Integer> nil = new Seq<Integer> () {
        public Integer head () {
            throw new RuntimeException();
        }
        public Seq<Integer> tail () {
            return this;
        }
    };

    public static <T> void print ( final Seq<T> seq ) {
        printPartSeq ( "[", seq.head(), seq.tail() );
    }

    private static <T> void printPartSeq ( final String prefix, final T value, final Seq<T> seq ) {
        if ( seq == nil) {
            System.out.println("]");
        } else {
            System.out.print(prefix);
            System.out.print(value);
            printPartSeq ( ",", seq.head(), seq.tail() );
        }
    }
}

3

कैसे परिवर्तनशील चर का उपयोग किए बिना उपयोगी जावा प्रोग्राम लिखने के लिए ।

आप केवल पुनरावृत्ति और कोई भी परिवर्तनशील चर का उपयोग करके जावा में किसी भी चीज़ के बारे में सिद्धांत लागू कर सकते हैं।

प्रयोग में:

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

  • पुस्तकालयों के लिए Ditto। और अगर आप अपने आप को पुस्तकालय की कक्षाओं तक सीमित कर लेते हैं, जो आवरण के नीचे उत्परिवर्तन का उपयोग नहीं करते हैं, तो यह और भी कठिन है। (आप स्ट्रिंग का उपयोग भी नहीं कर सकते ... कैसे hashcodeलागू किया गया है पर एक नज़र डालें ।)

  • मुख्यधारा के जावा कार्यान्वयन टेल-कॉल ऑप्टिमाइज़ेशन का समर्थन नहीं करते हैं। इसका अर्थ है कि एल्गोरिदम के पुनरावर्ती संस्करण स्टैक स्पेस "भूखे" होते हैं। और चूंकि जावा थ्रेड स्टैक नहीं बढ़ते हैं, इसलिए आपको बड़े स्टैक ... या जोखिम का प्रचार करने की आवश्यकता है StackOverflowError

इन तीनों चीजों को मिलाएं, और उत्परिवर्तनीय चर के बिना उपयोगी (यानी गैर-तुच्छ) प्रोग्राम लिखने के लिए जावा वास्तव में एक व्यवहार्य विकल्प नहीं है ।

(लेकिन हे, यह ठीक है। जेवीएम के लिए अन्य प्रोग्रामिंग भाषाएं उपलब्ध हैं, जिनमें से कुछ कार्यात्मक प्रोग्रामिंग का समर्थन करते हैं)


2

जैसा कि हम अवधारणाओं के उदाहरण के लिए देख रहे हैं, मैं कहूंगा कि आइए एक तरफ जावा सेट करें और एक अलग अभी तक परिचित सेटिंग की तलाश करें जिसमें अवधारणाओं का एक परिचित संस्करण मिल सके। UNIX पाइप आलसी कार्यों का पीछा करने के समान हैं।

cat /dev/zero | tr '\0' '\n' | cat -n | awk '{ print $0 * $0 }' | head 25

लिनक्स में इसका मतलब है, मुझे प्रत्येक बाइट दें, जो कि सच्चे बिट्स के बजाय झूठे से बना है, जब तक कि मैं अपनी भूख नहीं मिटाता; उन बाइट्स में से प्रत्येक को एक नए वर्ण में बदलें; इस प्रकार बनाई गई प्रत्येक पंक्ति की संख्या; और उस संख्या के वर्ग का उत्पादन करते हैं। इसके अलावा मैं 25 लाइनों के लिए भूख है और नहीं।

मेरा दावा है कि प्रोग्रामर को इस तरह से लिनक्स पाइपलाइन लिखने की सलाह नहीं दी जाएगी। यह अपेक्षाकृत सामान्य लिनक्स शेल स्क्रिप्टिंग है।

मेरा दावा है कि एक प्रोग्रामर को सलाह दी जाएगी कि वह जावा में भी इसी तरह लिखने का प्रयास करे। इसका कारण सॉफ्टवेयर परियोजनाओं के जीवनकाल लागत में एक प्रमुख कारक के रूप में सॉफ्टवेयर रखरखाव है। हम अगले प्रोग्रामर को दिखावा नहीं करना चाहते हैं कि ओस्टेंसिक रूप से जावा प्रोग्राम क्या है, लेकिन वास्तव में कस्टम वन-ऑफ लैंग्वेज में प्रभावी रूप से डुप्लिकेटिंग कार्यक्षमता के द्वारा लिखा जाता है जो पहले से ही जावा प्लेटफॉर्म में मौजूद है।

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

तब फिर से, यह जावा प्रोग्रामर के लिए कार्यात्मक प्रोग्रामिंग से प्रेरणा लेने के लिए एक अच्छा विचार हो सकता है।

हाल ही में मेरी पसंदीदा तकनीक [एक] एक अपरिवर्तनीय, अनइंस्टॉलिज्ड रिटर्न वैरिएबल और एक ही एग्जिट का उपयोग करने के लिए थी, जैसा कि कुछ कार्यात्मक भाषा संकलक करते हैं, जावा जाँचता है कि फ़ंक्शन के शरीर में कोई भी बात नहीं होती है, मैं हमेशा एक और केवल एक प्रदान करता हूं प्रतिलाभ की मात्रा। उदाहरण:

int f(final int n) {
    final int result; // not initialized here!
    if (n < 0) {
        result = -n;
    } else if (n < 1) {
        result = 0;
    } else {
        result = n - 1;
    }
    // If I would leave off the "else" clause,
    // Java would fail to compile complaining that
    // "result" is possibly uninitialized.
    return result;
}


मैं लगभग 70% निश्चित हूं जावा पहले से ही रिटर्न वैल्यू की जांच करता है। आपको "लापता रिटर्न स्टेटमेंट" के बारे में एक त्रुटि मिलनी चाहिए अगर नियंत्रण एक गैर-शून्य फ़ंक्शन के अंत से गिर सकता है।
cHao

मेरी बात: यदि आप इसे कोड करते हैं जैसा कि int result = -n; if (n < 1) { result = 0 } return result;यह ठीक ठीक संकलित करता है और संकलक को पता नहीं है कि आपने इसे मेरे उदाहरण में फ़ंक्शन के बराबर बनाने का इरादा किया है या नहीं। हो सकता है कि तकनीक को उपयोगी बनाने के लिए वह उदाहरण बहुत सरल है, लेकिन बहुत सारी शाखाओं के साथ एक फ़ंक्शन में मुझे यह स्पष्ट करना अच्छा लगता है कि परिणाम एक बार सौंपा गया है, भले ही कोई भी रास्ता हो।
मिनोपेट

यदि आप कहते हैं if (n < 1) return 0; else return -n;, हालांकि, आप कोई समस्या नहीं है ... और इसके अलावा सरल है। :) मुझे लगता है कि उस स्थिति में, "एक वापसी" नियम वास्तव में आपकी वापसी मूल्य निर्धारित किए जाने पर नहीं जानने के मुद्दे का कारण बनता है; अन्यथा, आप इसे वापस कर सकते हैं और जावा यह निर्धारित करने में अधिक सक्षम होगा कि क्या अन्य रास्तों से कोई मूल्य वापस नहीं आ सकता है, क्योंकि आप अब इसके वास्तविक रिटर्न से मूल्य की गणना को तलाक नहीं दे रहे हैं।
cHao

या, अपने जवाब के उदाहरण के लिए, if (n < 0) return -n; else if (n == 0) return 0; else return n - 1;
cHao

मैंने अभी निर्णय लिया है कि मैं अपने जीवन के किसी भी पल को जावा में ओनलीऑर्नटर्न नियम का बचाव नहीं करना चाहता। बाहर जाता है। जब और जब मैं एक जावा कोडिंग अभ्यास के बारे में सोचता हूं, तो मुझे लगता है कि मैं बचाव कर रहा हूं जो कि कार्यात्मक प्रोग्रामिंग प्रथाओं से प्रभावित है, मैं उस उदाहरण पर प्रतिस्थापन में डालूंगा। तब तक, कोई उदाहरण नहीं।
मिनोपेट

0

यह पता लगाने का सबसे आसान तरीका है कि फ्रीज कंपाइलर को निम्नलिखित फीड किया जाएगा , और उत्पन्न जावा कोड को देखें:

module Main where

result = take 25 (map sqr [1..]) where sqr x = x*x

कुछ दिनों के बाद मैंने अपने विचारों को इस उत्तर पर वापस पाया। मेरे सुझाव के सभी भाग के बाद स्काला में कार्यात्मक प्रोग्रामिंग भागों को लागू करना था। यदि हम उन स्थानों पर स्काला लगाने पर विचार करते हैं, जहां हमारे मन में वास्तव में हास्केल था (और मुझे लगता है कि मैं केवल एक ब्लॉग नहीं हूं ।zlemma.com/2013/02/20/… ), क्या हमें कम से कम फ्रीज पर विचार नहीं करना चाहिए?
मिनोपेट

@minopret यह वास्तव में आला फ्रेज ट्रेजेटिंग है - जो लोग हास्केल को जानते और प्यार करते हैं और अभी तक जेवीएम की जरूरत है। मुझे विश्वास है कि एक दिन फ्रीज कम से कम एक गंभीर विचार प्राप्त करने के लिए पर्याप्त परिपक्व होगा।
ingo
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.