कार्यात्मक प्रोग्रामिंग की परिभाषा
जॉय ऑफ क्लोजर का परिचय निम्नलिखित कहता है:
फ़ंक्शनल प्रोग्रामिंग उन कंप्यूटिंग टर्म्स में से एक है जिसमें एक अनाकार परिभाषा है। यदि आप 100 प्रोग्रामर से उनकी परिभाषा पूछते हैं, तो आपको 100 अलग-अलग उत्तर प्राप्त होंगे ...
कार्यात्मक प्रोग्रामिंग चिंताएं और कार्यों के अनुप्रयोग और संरचना की सुविधा देती हैं ... किसी भाषा को कार्यात्मक माना जाने के लिए, इसके कार्य की धारणा प्रथम श्रेणी में होनी चाहिए। प्रथम श्रेणी के कार्यों को डेटा के किसी भी अन्य टुकड़े की तरह ही संग्रहीत, पास और लौटाया जा सकता है। इस मूल अवधारणा से परे, [एफपी की परिभाषाएँ शामिल हो सकती हैं] शुद्धता, अपरिवर्तनीयता, पुनरावृत्ति, आलस्य और संदर्भात्मक पारदर्शिता।
स्केल 2 एडिशन पी में प्रोग्रामिंग । 10 की निम्नलिखित परिभाषा है:
कार्यात्मक प्रोग्रामिंग दो मुख्य विचारों द्वारा निर्देशित है। पहला विचार यह है कि फ़ंक्शंस प्रथम श्रेणी के मान हैं ... आप फ़ंक्शंस को अन्य फ़ंक्शंस के रूप में पास कर सकते हैं, फ़ंक्शंस के परिणाम के रूप में उन्हें वापस कर सकते हैं या उन्हें वेरिएबल्स में स्टोर कर सकते हैं ...
फ़ंक्शनल प्रोग्रामिंग का दूसरा मुख्य विचार यह है कि किसी प्रोग्राम के संचालन को इनपुट वैल्यूज़ को आउटपुट वैल्यू में बदलने के बजाय डेटा में बदलाव करना चाहिए।
यदि हम पहली परिभाषा को स्वीकार करते हैं, तो आपको अपने कोड को "कार्यात्मक" बनाने के लिए केवल एक चीज की आवश्यकता होती है, ताकि आप अपने छोरों को अंदर बाहर कर सकें। दूसरी परिभाषा में अपरिवर्तनीयता शामिल है।
प्रथम श्रेणी के कार्य
आप वर्तमान में अपने बस ऑब्जेक्ट से यात्रियों की एक सूची प्राप्त करते हैं और आप बस किराया की राशि से प्रत्येक यात्री के बैंक खाते को कम करने पर इसे पुन: व्यवस्थित करते हैं। इसी क्रिया को करने का कार्यात्मक तरीका बस में एक विधि होना होगा, शायद जिसे फॉरचैन्जर कहा जाता है जो एक तर्क का कार्य करता है। फिर बस अपने यात्रियों के ऊपर से पुनरावृत्ति करेगा, लेकिन यह सबसे अच्छा है और आपका ग्राहक कोड जो सवारी के लिए किराया वसूल करता है, उसे एक समारोह में रखा जाएगा और forEachPassenger को पास किया जाएगा। देखा! आप कार्यात्मक प्रोग्रामिंग का उपयोग कर रहे हैं।
अनिवार्य:
for (Passenger p : Bus.getPassengers()) {
p.debit(fare);
}
फ़ंक्शनल (एक गुमनाम फ़ंक्शन या स्काला में "लैम्ब्डा" का उपयोग करके):
myBus = myBus.forEachPassenger(p:Passenger -> { p.debit(fare) })
अधिक मीठा स्काला संस्करण:
myBus = myBus.forEachPassenger(_.debit(fare))
गैर-प्रथम श्रेणी के कार्य
यदि आपकी भाषा प्रथम श्रेणी के कार्यों का समर्थन नहीं करती है, तो यह बहुत बदसूरत हो सकती है। जावा 7 या उससे पहले, आपको इस तरह एक "कार्यात्मक वस्तु" इंटरफ़ेस प्रदान करना होगा:
// Java 8 has java.util.function.Consumer, but in earlier
// versions you have to roll your own:
public interface Consumer<T> {
public void accept(T t);
}
तब बस वर्ग एक आंतरिक पुनरावृत्ति प्रदान करता है:
public void forEachPassenger(Consumer<Passenger> c) {
for (Passenger p : passengers) {
c.accept(p);
}
}
अंत में, आप बस के लिए एक अनाम फ़ंक्शन ऑब्जेक्ट पास करते हैं:
// Java 8 has syntactic sugar to make this look more like
// the Scala solution, but earlier versions require manually
// instantiating a "Function Object," in this case, a
// Consumer:
Bus.forEachPassenger(new Consumer<Passenger>() {
@Override
public void accept(final Passenger p) {
p.debit(fare);
}
}
जावा 8 स्थानीय चर को एक अनाम फ़ंक्शन के दायरे पर कब्जा करने की अनुमति देता है, लेकिन पहले के संस्करणों में, ऐसे किसी भी चर को अंतिम घोषित किया जाना चाहिए। इसके आस-पास जाने के लिए आपको MutableReference आवरण कक्षा बनाने की आवश्यकता हो सकती है। यहाँ एक पूर्णांक-विशिष्ट वर्ग है जो आपको उपरोक्त कोड में एक लूप काउंटर जोड़ने देता है:
public static class MutableIntWrapper {
private int i;
private MutableIntWrapper(int in) { i = in; }
public static MutableIntWrapper ofZero() {
return new MutableIntWrapper(0);
}
public int value() { return i; }
public void increment() { i++; }
}
final MutableIntWrapper count = MutableIntWrapper.ofZero();
Bus.forEachPassenger(new Consumer<Passenger>() {
@Override
public void accept(final Passenger p) {
p.debit(fare);
count.increment();
}
}
System.out.println(count.value());
इस कुरूपता के साथ, कभी-कभी आंतरिक पुनरावृत्ति प्रदान करके आपके पूरे कार्यक्रम में फैले लूप से जटिल और दोहराया तर्क को समाप्त करना फायदेमंद होता है।
यह कुरूपता जावा 8 में तय की गई है, लेकिन एक प्रथम श्रेणी फ़ंक्शन के अंदर जाँच किए गए अपवादों को संभालना अभी भी वास्तव में बदसूरत है और जावा अभी भी अपने सभी संग्रहों में परिवर्तनशीलता की धारणा को वहन करता है। जो हमें अक्सर एफपी से जुड़े अन्य लक्ष्यों के लिए लाता है:
अचल स्थिति
जोश बलोच का आइटम 13 "प्राथमिकता को प्राथमिकता दें।" इसके विपरीत आम कचरा बात के बावजूद, OOP को अपरिवर्तनीय वस्तुओं के साथ किया जा सकता है, और ऐसा करने से यह बहुत बेहतर होता है। उदाहरण के लिए, जावा में स्ट्रिंग अपरिवर्तनीय है। StringBuffer, OTOH को एक अपरिवर्तनीय स्ट्रिंग बनाने के लिए परस्पर योग्य होना चाहिए। कुछ कार्यों, जैसे कि बफ़र्स के साथ काम करना स्वाभाविक रूप से पारस्परिकता की आवश्यकता होती है।
पवित्रता
प्रत्येक फ़ंक्शन कम से कम यादगार होना चाहिए - यदि आप इसे एक ही इनपुट पैरामीटर देते हैं (और इसके वास्तविक तर्कों के अलावा कोई इनपुट नहीं होना चाहिए), तो यह हर बार एक ही आउटपुट का उत्पादन किए बिना "साइड इफेक्ट्स" जैसे कि बदलती वैश्विक स्थिति, प्रदर्शन कर रहा है। / ओ, या अपवादों को फेंकना।
यह कहा गया है कि कार्यात्मक प्रोग्रामिंग में, "काम करने के लिए आमतौर पर कुछ बुराई की आवश्यकता होती है।" 100% शुद्धता आमतौर पर लक्ष्य नहीं है। कम से कम दुष्प्रभाव है।
निष्कर्ष
वास्तव में, उपरोक्त सभी विचारों में, मेरे कोड को सरल बनाने के लिए व्यावहारिक अनुप्रयोगों के संदर्भ में अपरिवर्तनीयता सबसे बड़ी एकल जीत है - चाहे ओओपी, या एफपी। पुनरावृत्तियों के लिए कार्य करना दूसरी सबसे बड़ी जीत है। जावा 8 lambdas प्रलेखन क्यों का सबसे अच्छा व्याख्या है। पेड़ों की प्रोसेसिंग के लिए रिकर्सन शानदार है। आलस्य आपको अनंत संग्रहों के साथ काम करने की अनुमति देता है।
यदि आप जेवीएम को पसंद करते हैं, तो मैं आपको स्काला और क्लोजर पर एक नज़र डालने की सलाह देता हूं। दोनों कार्यात्मक प्रोग्रामिंग के व्यावहारिक व्याख्याएं हैं। Scala कुछ C- जैसे सिंटैक्स के साथ टाइप-सुरक्षित है, हालांकि यह वास्तव में सी के साथ हास्केल के रूप में बहुत अधिक सिंटैक्स है। सी। क्लोजर टाइप-सुरक्षित नहीं है और यह एक लिस्प है। मैंने हाल ही में जावा, स्काला और क्लोजर की तुलना एक विशिष्ट रीफैक्टरिंग समस्या के संबंध में की है। लोगन कैंपबेल की गेम ऑफ लाइफ का उपयोग करके तुलना में हास्केल और टाइप क्लोजर भी शामिल हैं।
पुनश्च
जिमी हॉफ ने बताया कि मेरी बस की क्लास म्यूट है। मूल को ठीक करने के बजाय, मुझे लगता है कि यह वास्तव में इस प्रश्न के बारे में जिस तरह का प्रतिबिंबन है, वह प्रदर्शित करेगा। यह एक नई बस का उत्पादन करने के लिए एक कारखाने बस में प्रत्येक विधि बनाकर तय किया जा सकता है, प्रत्येक विधि यात्री पर एक नया यात्री उत्पादन करने के लिए एक कारखाना है। इस प्रकार मैंने हर चीज में एक रिटर्न टाइप जोड़ा है जिसका अर्थ है कि मैं Java 8 के java.util.function की नकल करूंगा। उपभोक्ता इंटरफ़ेस के बजाय।
public interface Function<T,R> {
public R apply(T t);
// Note: I'm leaving out Java 8's compose() method here for simplicity
}
फिर बस में:
public Bus mapPassengers(Function<Passenger,Passenger> c) {
// I have to use a mutable collection internally because Java
// does not have immutable collections that return modified copies
// of themselves the way the Clojure and Scala collections do.
List<Passenger> newPassengers = new ArrayList(passengers.size());
for (Passenger p : passengers) {
newPassengers.add(c.apply(p));
}
return Bus.of(driver, Collections.unmodifiableList(passengers));
}
अंत में, अनाम फ़ंक्शन ऑब्जेक्ट चीजों की संशोधित स्थिति (नए यात्रियों के साथ एक नई बस) लौटाता है। यह मानता है कि p.debit () अब मूल की तुलना में कम पैसे के साथ एक नया अपरिवर्तनीय यात्री देता है:
Bus b = b.mapPassengers(new Function<Passenger,Passenger>() {
@Override
public Passenger apply(final Passenger p) {
return p.debit(fare);
}
}
उम्मीद है कि अब आप अपना निर्णय ले सकते हैं कि आप अपनी अनिवार्य भाषा को कितना कार्यात्मक बनाना चाहते हैं, और यह तय करें कि एक कार्यात्मक भाषा का उपयोग करके अपनी परियोजना को नया स्वरूप देना बेहतर होगा। स्काला या क्लोजर में, संग्रह और अन्य एपीआई को कार्यात्मक प्रोग्रामिंग को आसान बनाने के लिए डिज़ाइन किया गया है। दोनों में बहुत अच्छा जावा इंटरॉप है, जिससे आप भाषाओं का मिश्रण और मिलान कर सकते हैं। वास्तव में, जावा इंटरऑपरेबिलिटी के लिए, स्काला अपने प्रथम श्रेणी के कार्यों को उन अनाम कक्षाओं में संकलित करता है जो जावा 8 कार्यात्मक इंटरफेस के साथ लगभग संगत हैं। आप स्काला में गहराई से संप्रदाय में विवरण के बारे में पढ़ सकते हैं । १.३.२ ।