करीने से क्या फायदा है?


154

मैंने सिर्फ करीने के बारे में सीखा है, और जब मुझे लगता है कि मैं अवधारणा को समझता हूं, तो मुझे इसका उपयोग करने में कोई बड़ा फायदा नहीं दिख रहा है।

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

fun add(x, y) = x + y

और कहा जाता है

add(3, 5)

जबकि करी संस्करण है

fun add x y = x + y 
(* short for val add = fn x => fn y=> x + y *)

और कहा जाता है

add 3 5

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

क्या थोड़ा सरल वाक्यविन्यास केवल करी के लिए प्रेरणा है, या क्या मुझे कुछ अन्य फायदे याद आ रहे हैं जो मेरे बहुत सरल उदाहरण में स्पष्ट नहीं हैं? क्या सिर्फ़ सिंटैक्टिक शुगर की करी है?


54
अकेले करीना अनिवार्य रूप से बेकार है, लेकिन डिफ़ॉल्ट रूप से करी गई सभी फ़ंक्शन होने से उपयोग करने के लिए कई अन्य विशेषताएं बहुत अच्छे हो जाती हैं। जब तक आपने वास्तव में कुछ समय के लिए एक कार्यात्मक भाषा का उपयोग नहीं किया है तब तक इसकी सराहना करना कठिन है।
सीए मैककैन

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

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

@ManojR सभी ने हास्केल में उदाहरण नहीं दिया है।
phwd

1
सवाल ने Reddit पर एक काफी दिलचस्प चर्चा उत्पन्न की ।
यानि

जवाबों:


126

करी कार्यों के साथ आपको और अधिक अमूर्त कार्यों का पुन: उपयोग आसान हो जाता है, क्योंकि आप विशेषज्ञ होते हैं। मान लीजिए कि आपके पास एक जोड़ने का कार्य है

add x y = x + y

और आप किसी सूची के प्रत्येक सदस्य में 2 जोड़ना चाहते हैं। हास्केल में आप ऐसा करेंगे:

map (add 2) [1, 2, 3] -- gives [3, 4, 5]
-- actually one could just do: map (2+) [1, 2, 3], but that may be Haskell specific

यहां सिंटैक्स हल्का होता है, यदि आपको कोई फ़ंक्शन बनाना था add2

add2 y = add 2 y
map add2 [1, 2, 3]

या अगर आपको एक अनाम मेमना समारोह करना था:

map (\y -> 2 + y) [1, 2, 3]

यह आपको अलग-अलग कार्यान्वयन से दूर करने की अनुमति देता है। मान लें कि आपके पास दो लुकअप फ़ंक्शन हैं। की / वैल्यू पेयर की लिस्ट में से एक और एक वैल्यू की एक चाबी और दूसरा की से कीज़ से वैल्यूज़ और वैल्यू की एक की-से-एक वैल्यू:

lookup1 :: [(Key, Value)] -> Key -> Value -- or perhaps it should be Maybe Value
lookup2 :: Map Key Value -> Key -> Value

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

myFunc :: (Key -> Value) -> .....

निष्कर्ष में: करी अच्छा है, क्योंकि यह आपको हल्के वाक्यविन्यास का उपयोग करके कार्य करने / आंशिक रूप से लागू करने देता है और फिर इन आंशिक रूप से लागू किए गए कार्यों को उच्च आदेश फ़ंक्शन के लिए mapया जैसे पास करता है filter। उच्चतर ऑर्डर फ़ंक्शंस (जो फ़ंक्शंस के रूप में फ़ंक्शंस लेते हैं या परिणाम के रूप में उन्हें उपज देते हैं) कार्यात्मक प्रोग्रामिंग की रोटी और मक्खन हैं, और करी और आंशिक रूप से लागू फ़ंक्शंस उच्च ऑर्डर फ़ंक्शंस को अधिक प्रभावी और संक्षिप्त रूप से उपयोग करने में सक्षम बनाते हैं।


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

वाट। "की / वैल्यू पेयर की लिस्ट में से एक और वैल्यू की कुंजी और दूसरे में मैप से कीज़ से वैल्यूज़ और वैल्यू की कुंजी"
Mateen Ulhaq

@MateenUlhaq यह पिछले वाक्य की एक निरंतरता है, जहां मुझे लगता है कि हम एक कुंजी के आधार पर एक मूल्य प्राप्त करना चाहते हैं, और हमारे पास ऐसा करने के दो तरीके हैं। वाक्य उन दो तरीकों की गणना करता है। पहले तरीके से, आपको कुंजी / मान जोड़े की एक सूची दी गई है और जिस कुंजी के लिए हम मूल्य ढूंढना चाहते हैं, और दूसरे तरीके से हमें एक उचित नक्शा दिया गया है, और फिर से एक कुंजी। यह वाक्य के तुरंत बाद कोड को देखने में मदद कर सकता है।
बोरिस

53

व्यावहारिक उत्तर यह है कि करीने से गुमनाम कार्यों को बनाने में बहुत आसानी होती है। यहां तक ​​कि एक न्यूनतम लंबोदर सिंटैक्स के साथ, यह एक जीत के कुछ है; तुलना:

map (add 1) [1..10]
map (\ x -> add 1 x) [1..10]

यदि आपके पास एक बदसूरत लैम्ब्डा सिंटैक्स है, तो यह और भी बुरा है। (मैं तुम्हें, जावास्क्रिप्ट, योजना और अजगर को देख रहा हूं।)

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

अधिक मौलिक रूप से, यह हमेशा स्पष्ट नहीं होता है कि फ़ंक्शन का कौन सा संस्करण "विहित" है। उदाहरण के लिए, ले लो map। दो प्रकार से mapलिखा जा सकता है:

map :: (a -> b) -> [a] -> [b]
map :: (a -> b) -> ([a] -> [b])

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

यह विशेष रूप से महत्वपूर्ण हो जाता है जब आप mapसूची के अलावा अन्य प्रकारों के लिए सामान्यीकरण करते हैं।

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

बेशक, एमएल-शैली की भाषाओं में करीने या अविवाहित रूप में कई तर्कों की धारणा नहीं है। f(a, b, c)वाक्य रचना वास्तव में टपल में गुजर से मेल खाती है (a, b, c)में fहै, इसलिए fअभी तक केवल तर्क पर ले जाता है। यह वास्तव में एक बहुत ही उपयोगी अंतर है जो मैं चाहता हूं कि अन्य भाषाएं होंगी क्योंकि यह कुछ लिखना बहुत स्वाभाविक है:

map f [(1,2,3), (4,5,6), (7, 8, 9)]

आप आसानी से भाषाओं के साथ ऐसा नहीं कर सकते हैं, जिसमें कई तर्क सही हैं।


1
"एमएल-शैली की भाषाओं में करीने से या अपरिष्कृत रूप में कई तर्कों की धारणा नहीं है": इस संबंध में, हास्केल एमएल-शैली है?
जियोर्जियो

1
@ जियोर्जियो: हाँ।
तिकोन जेल्विस

1
दिलचस्प। मैं कुछ हास्केल जानता हूं और मैं अभी SML सीख रहा हूं, इसलिए दोनों भाषाओं के बीच अंतर और समानता देखना दिलचस्प है।
जियोर्जियो

शानदार उत्तर, और अगर आप अभी भी आश्वस्त नहीं हैं तो यूनिक्स पाइपलाइनों के बारे में सोचें जो लंबोदर धाराओं के समान हैं
श्रीधर सरनोबत

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

24

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

इसे पूरा करने के लिए कोड सरल होने जा रहा है यदि आपको पहले सभी मापदंडों को एक साथ लाने की आवश्यकता है।

इसके अलावा, अधिक कोड पुन: उपयोग की संभावना है, क्योंकि एकल पैरामीटर (एक और क्यूरेटेड फ़ंक्शन) लेने वाले फ़ंक्शन को विशेष रूप से सभी मापदंडों के साथ मेल नहीं खाता है।


14

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


2
जबकि यहाँ प्रेरणा सैद्धांतिक है, मुझे लगता है कि सादगी लगभग हमेशा एक व्यावहारिक लाभ है। बहु-तर्क कार्यों के बारे में चिंता न करना मेरे कार्यक्रम के दौरान मेरे जीवन को आसान बनाता है, जैसे कि अगर मैं शब्दार्थ के साथ काम कर रहा होता।
तिखन जेल्विस

2
@TikhonJelvis जब आप प्रोग्रामिंग कर रहे होते हैं, तब भी, करीने से आपको चिंता करने के लिए अन्य चीजें मिलती हैं, जैसे कि कंपाइलर इस तथ्य को पकड़ नहीं रहा है कि आपने किसी फ़ंक्शन के लिए बहुत कम तर्क पारित किए हैं, या उस मामले में एक खराब त्रुटि संदेश भी प्राप्त कर रहा है; जब आप करी का उपयोग नहीं करते हैं, तो त्रुटि बहुत अधिक स्पष्ट होती है।
एलेक्स आर

मुझे कभी इस तरह की समस्या नहीं हुई है: जीएचसी, बहुत कम से कम, इस संबंध में बहुत अच्छा है। कंपाइलर हमेशा उस तरह के मुद्दे को पकड़ता है, और इस त्रुटि के लिए भी अच्छे त्रुटि संदेश हैं।
तिखन जेल्विस

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

14

(मैं हास्केल में उदाहरण दूंगा।)

  1. कार्यात्मक भाषाओं का उपयोग करते समय यह बहुत सुविधाजनक है कि आप आंशिक रूप से एक फ़ंक्शन लागू कर सकते हैं। जैसे हास्केल का (== x)एक ऐसा फंक्शन है जो रिटर्न करता है Trueयदि इसका तर्क दिए गए शब्द के बराबर है x:

    mem :: Eq a => a -> [a] -> Bool
    mem x lst = any (== x) lst
    

    बिना करी के, हमारे पास कुछ कम पठनीय कोड होगा:

    mem x lst = any (\y -> y == x) lst
    
  2. यह टैसिट प्रोग्रामिंग से संबंधित है ( हास्केल विकी पर पॉइंटफ्री शैली भी देखें )। यह शैली चरों द्वारा दर्शाए गए मानों पर नहीं, बल्कि रचनाओं पर और कार्यों की श्रृंखला के माध्यम से जानकारी कैसे प्रवाहित होती है, पर केंद्रित है। हम अपने उदाहरण को ऐसे रूप में परिवर्तित कर सकते हैं जो चर का उपयोग बिल्कुल नहीं करता है:

    mem = any . (==)
    

    यहाँ हम देखने ==से एक समारोह के रूप aको a -> Boolऔर anyसे एक समारोह के रूप a -> Boolके लिए [a] -> Bool। बस उन्हें कंपोज़ करके, हमें उसका परिणाम मिलता है। यह सब करी के लिए धन्यवाद है।

  3. रिवर्स, अन-करी, कुछ स्थितियों में भी उपयोगी है। उदाहरण के लिए, मान लें कि हम एक सूची को दो भागों में विभाजित करना चाहते हैं - ऐसे तत्व जो 10 और बाकी हिस्सों से छोटे हैं, और फिर उन दो सूचियों को समेटते हैं। सूची का विभाजन इसके द्वारा किया जाता है (यहां हम भी करी का उपयोग करते हैं )। परिणाम प्रकार का है । परिणाम को उसके पहले और दूसरे भाग में निकालने और उनका उपयोग करने के संयोजन के बजाय , हम इसे सीधे रूप में अनसुना करके कर सकते हैंpartition (< 10)<([Int],[Int])++++

    uncurry (++) . partition (< 10)
    

दरअसल, (uncurry (++) . partition (< 10)) [4,12,11,1]मूल्यांकन करता है [4,1,12,11]

वहाँ भी महत्वपूर्ण सैद्धांतिक लाभ हैं:

  1. क्यारी करना उन भाषाओं के लिए आवश्यक है, जिनमें डेटा प्रकारों की कमी होती है और केवल फ़ंक्शन होते हैं, जैसे कि लैम्ब्डा कैलकुलस । हालांकि ये भाषाएं व्यावहारिक उपयोग के लिए उपयोगी नहीं हैं, वे सैद्धांतिक दृष्टिकोण से बहुत महत्वपूर्ण हैं।
  2. यह कार्यात्मक भाषाओं की आवश्यक संपत्ति के साथ जुड़ा हुआ है - कार्य प्रथम श्रेणी के ऑब्जेक्ट हैं। हमने देखा के रूप में, से रूपांतरण (a, b) -> cकरने के लिए a -> (b -> c)इसका मतलब है कि बाद के फ़ंक्शन के परिणाम इस प्रकार का है b -> c। दूसरे शब्दों में, परिणाम एक कार्य है।
  3. (Un) करींग कार्टेशियन क्लोज्ड कैटेगरीज से निकटता से जुड़ा हुआ है , जो टाइप किए हुए लंबो कैल्कुली देखने का एक सरल तरीका है।

"बहुत कम पठनीय कोड" बिट के लिए, क्या ऐसा नहीं होना चाहिए mem x lst = any (\y -> y == x) lst? (बैकलैश के साथ)।
स्टुसमिथ

हाँ, यह इंगित करने के लिए धन्यवाद, मैं इसे सही करूँगा।
पेट्र पुडलक

9

शकरकंद केवल शकरकंद नहीं है!

add1(अपरिचित) और add2(करी) के हस्ताक्षर पर विचार करें :

add1 : (int * int) -> int
add2 : int -> (int -> int)

(दोनों मामलों में, टाइप सिग्नेचर में कोष्ठक वैकल्पिक हैं, लेकिन मैंने उन्हें स्पष्टता के लिए शामिल किया है।)

add1एक समारोह की एक 2-टपल लेता है intऔर intऔर एक रिटर्न intadd2एक फ़ंक्शन है जो एक लेता है intऔर एक और फ़ंक्शन देता है जो बदले में एक लेता है intऔर एक रिटर्न देता है int

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

apply(f, b) = f b

अब हम अंतर add1और add2अधिक स्पष्ट रूप से देख सकते हैं । add12-ट्यूपल के साथ बुलाया जाता है:

apply(add1, (3, 5))

लेकिन add2एक के साथ बुलाया जाता है int और फिर उसका रिटर्न मान दूसरे के साथ कहा जाता हैint :

apply(apply(add2, 3), 5)

EDIT: करीने का आवश्यक लाभ यह है कि आपको मुफ्त में आंशिक आवेदन मिलता है। कहते हैं कि आप एक प्रकार का कार्य चाहते थे int -> int(कहते हैं, mapयह एक सूची में) जिसने अपने पैरामीटर में 5 जोड़ा। आप लिख सकते हैं addFiveToParam x = x+5, या आप एक इनलाइन लैम्ब्डा के साथ बराबर कर सकते हैं, लेकिन आप बहुत अधिक आसानी से भी कर सकते हैं (विशेष रूप से इस एक से कम तुच्छ मामलों में) लिखें add2 5!


3
मैं समझता हूं कि मेरे उदाहरण के लिए पर्दे के पीछे एक बड़ा अंतर है, लेकिन परिणाम एक साधारण वाक्य परिवर्तन लगता है।
पागल वैज्ञानिक

5
करीना बहुत गहरी अवधारणा नहीं है। यह अंतर्निहित मॉडल को सरल बनाने के बारे में है (लैम्ब्डा कैलकुलस देखें) या उन भाषाओं में, जिनके पास वैसे भी ट्यूपल्स हैं, यह वास्तव में आंशिक अनुप्रयोग की सिंटैक्टिक सुविधा के बारे में है। वाक्यात्मक सुविधा के महत्व को कम मत समझो।
13

9

क्यारी बनाना सिर्फ चीनी है, लेकिन आप थोड़ा गलत समझ रहे हैं कि चीनी क्या करती है, मुझे लगता है। अपना उदाहरण लेते हुए,

fun add x y = x + y

वास्तव में के लिए कृत्रिम चीनी है

fun add x = fn y => x + y

यही है, (x जोड़ें) एक फ़ंक्शन देता है जो एक तर्क y लेता है, और x को y जोड़ता है।

fun addTuple (x, y) = x + y

यह एक फ़ंक्शन है जो एक टपल लेता है और अपने तत्वों को जोड़ता है। वे दो कार्य वास्तव में काफी भिन्न हैं; वे अलग-अलग तर्क देते हैं।

यदि आप किसी सूची में सभी संख्याओं में 2 जोड़ना चाहते हैं:

(* add 2 to all numbers using the uncurried function *)
map (fn x => addTuple (x, 2)) [1,2,3]
(* using the curried function *)
map (add 2) [1,2,3]

परिणाम होगा [3,4,5]

यदि आप प्रत्येक ट्यूपल को एक सूची में जोड़ना चाहते हैं, तो दूसरी ओर, AddTuple फ़ंक्शन पूरी तरह से फिट बैठता है।

(* Sum each tuple using the uncurried function *)
map addTuple [(10,2), (10,3), (10,4)]    
(* sum each tuple using curried function *)
map (fn (a,b) => add a b) [(10,2), (10,3), (10,4)]

परिणाम होगा [12,13,14]

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

- val highestPositive = foldr Int.max 0;   
val highestPositive = fn : int list -> int 

1
मुझे समझ में आया कि करी फ़ंक्शन का एक अलग प्रकार का हस्ताक्षर है, और यह वास्तव में एक फ़ंक्शन है जो किसी अन्य फ़ंक्शन को लौटाता है। मैं आंशिक आवेदन भाग को याद कर रहा था।
पागल वैज्ञानिक

9

एक और बात जिसका मैंने अभी तक उल्लेख नहीं किया है, वह यह है कि करी पर अंकुश लगाने (सीमित) की अनुमति देता है।

इन कार्यों पर विचार करें जो हास्केल के पुस्तकालय का हिस्सा हैं

(.) :: (b -> c) -> (a -> b) -> a -> c
either :: (a -> c) -> (b -> c) -> Either a b -> c
flip :: (a -> b -> c) -> b -> a -> c
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

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


6

मेरी सीमित समझ ऐसी है:

1) आंशिक समारोह आवेदन

आंशिक फ़ंक्शन अनुप्रयोग एक फ़ंक्शन को वापस करने की प्रक्रिया है जो कम संख्या में तर्क लेती है। यदि आप 3 में से 2 तर्क प्रदान करते हैं, तो यह 3-2 = 1 तर्क लेता है। यदि आप 3 तर्कों में से 1 प्रदान करते हैं, तो यह 3-1 = 2 तर्कों को लेने वाला फ़ंक्शन लौटाएगा। यदि आप चाहते हैं, तो आप आंशिक रूप से 3 में से 3 तर्कों को भी लागू कर सकते हैं और यह एक ऐसा फ़ंक्शन लौटाएगा जिसमें कोई तर्क नहीं है।

तो निम्न कार्य दिया:

f(x,y,z) = x + y + z;

जब 1 से x को बाँधना और आंशिक रूप से उपरोक्त फ़ंक्शन पर लागू करना है जो f(x,y,z)आपको मिलेगा:

f(1,y,z) = f'(y,z);

कहाँ पे: f'(y,z) = 1 + y + z;

अब यदि आप y को 2 और z को 3 से बाँधते हैं, और आंशिक रूप से लागू होते f'(y,z)हैं:

f'(2,3) = f''();

कहाँ f''() = 1 + 2 + 3:;

अब किसी भी बिंदु पर, आप मूल्यांकन करना चुन सकते हैं f, f'या f''। तो मैं कर सकता हूँ:

print(f''()) // and it would return 6;

या

print(f'(1,1)) // and it would return 3;

2) करी

दूसरी ओर क्यूरिंग एक फ़ंक्शन को एक तर्क फ़ंक्शन के नेस्टेड श्रृंखला में विभाजित करने की प्रक्रिया है। आप कभी भी 1 से अधिक तर्क नहीं दे सकते, यह एक या शून्य है।

तो एक ही कार्य दिया:

f(x,y,z) = x + y + z;

यदि आप इसे कर लेते हैं, तो आपको 3 कार्यों की एक श्रृंखला मिलेगी:

f'(x) -> f''(y) -> f'''(z)

कहाँ पे:

f'(x) = x + f''(y);

f''(y) = y + f'''(z);

f'''(z) = z;

अब अगर आप कॉल f'(x)करते हैं x = 1:

f'(1) = 1 + f''(y);

आपको एक नया फ़ंक्शन लौटाया गया है:

g(y) = 1 + f''(y);

यदि आप फोन g(y)करते हैं y = 2:

g(2) = 1 + 2 + f'''(z);

आपको एक नया फ़ंक्शन लौटाया गया है:

h(z) = 1 + 2 + f'''(z);

अंत में अगर आप कॉल h(z)करते हैं z = 3:

h(3) = 1 + 2 + 3;

आपको लौटा दिया जाता है 6

३) बंद होना

अंत में, क्लोजर एक इकाई के रूप में एक फ़ंक्शन और डेटा को एक साथ कैप्चर करने की प्रक्रिया है। एक फ़ंक्शन क्लोजर में 0 से अनंत संख्या में तर्क हो सकते हैं, लेकिन यह उस डेटा के बारे में भी जानता है जो इसके पास नहीं गया है।

फिर से, समान कार्य दिया गया:

f(x,y,z) = x + y + z;

आप इसके बजाय एक बंद लिख सकते हैं:

f(x) = x + f'(y, z);

कहाँ पे:

f'(y,z) = x + y + z;

f'पर बंद है x। मतलब कि f'जो x के अंदर है उसका मूल्य पढ़ सकते हैं f

तो अगर आप के fसाथ कॉल करने के लिए गए थे x = 1:

f(1) = 1 + f'(y, z);

आपको एक बंद मिलेगा:

closureOfF(y, z) =
                   var x = 1;
                   f'(y, z);

अब अगर आप के closureOfFसाथ बुलाया y = 2और z = 3:

closureOfF(2, 3) = 
                   var x = 1;
                   x + 2 + 3;

जो लौटेगा 6

निष्कर्ष

करी, आंशिक अनुप्रयोग और क्लोजर सभी कुछ समान हैं कि वे एक फ़ंक्शन को अधिक भागों में विघटित करते हैं।

एकल तर्कों के नेस्टेड फ़ंक्शंस में कई तर्कों के फ़ंक्शन को करीने से विघटित करता है जो एकल तर्कों के कार्यों को वापस करते हैं। एक या कम तर्क के कार्य को पूरा करने का कोई मतलब नहीं है, क्योंकि इसका कोई मतलब नहीं है।

आंशिक आवेदन कम तर्कों के एक समारोह में कई तर्कों का एक कार्य विघटित करता है जिनकी अब लापता दलीलों को आपूर्ति मूल्य के लिए प्रतिस्थापित किया गया था।

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

इन सभी के बारे में क्या भ्रम है कि वे दूसरों के सबसेट को लागू करने के लिए प्रत्येक का उपयोग कर सकते हैं। तो संक्षेप में, वे सभी कार्यान्वयन विवरण के एक बिट हैं। वे सभी समान मूल्य प्रदान करते हैं कि आपको सभी मूल्यों को अग्रिम रूप से इकट्ठा करने की आवश्यकता नहीं होती है और इसमें आप फ़ंक्शन का हिस्सा पुन: उपयोग कर सकते हैं, क्योंकि आपने इसे विखंडित इकाइयों में विघटित कर दिया है।

प्रकटीकरण

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


1
तो जवाब है: करीने से फायदा नहीं हुआ?
छत

1
@ ऐसिंग जहाँ तक मुझे पता है, यह सही है। अभ्यास में करी और आंशिक अनुप्रयोग आपको समान लाभ देंगे। किसी भाषा में लागू करने का विकल्प कार्यान्वयन के कारणों के लिए किया जाता है, एक को लागू करना आसान हो सकता है, फिर किसी अन्य को एक निश्चित भाषा दी जाती है।
डिडियर ए।

5

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

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

इसके बजाय (lambda (x) (+ 3 x)), जो हमें एक फ़ंक्शन देता है जो इसके तर्क में 3 जोड़ता है, आप कुछ ऐसा लिख ​​सकते हैं (op + 3), और इसलिए कुछ सूची के प्रत्येक तत्व में 3 को जोड़ने के (mapcar (op + 3) some-list)बजाय तब होगा (mapcar (lambda (x) (+ 3 x)) some-list)। यह opमैक्रो आपको एक फ़ंक्शन देगा जो कुछ तर्क x y z ...और आह्वान लेता है (+ a x y z ...)

कई विशुद्ध रूप से कार्यात्मक भाषाओं में, आंशिक अनुप्रयोग को सिंटैक्स में जोड़ा जाता है ताकि कोई opऑपरेटर न हो। आंशिक अनुप्रयोग को ट्रिगर करने के लिए, आपको बस एक फ़ंक्शन को कम तर्कों के साथ कॉल करना होगा जो इसे आवश्यक है। "insufficient number of arguments"त्रुटि उत्पन्न करने के बजाय , परिणाम शेष तर्कों का एक कार्य है।


"करीयनिंग ... आपको कुछ मापदंडों को तय करके एक नया फ़ंक्शन बनाने देता है" - नहीं, प्रकार के एक फ़ंक्शन में a -> b -> cपैरामीटर s (बहुवचन) नहीं है, इसका केवल एक पैरामीटर है c,। जब कहा जाता है, तो यह प्रकार का एक फ़ंक्शन देता है a -> b
मैक्स हेइबर

4

समारोह के लिए

fun add(x, y) = x + y

यह रूप का है f': 'a * 'b -> 'c

मूल्यांकन करने के लिए एक करना होगा

add(3, 5)
val it = 8 : int

करी समारोह के लिए

fun add x y = x + y

मूल्यांकन करने के लिए एक करना होगा

add 3
val it = fn : int -> int

जहां यह एक आंशिक संगणना है, विशेष रूप से (3 + y), जो तब संगणना को पूरा कर सकता है

it 5
val it = 8 : int

दूसरे मामले में जोड़ें फॉर्म का है f: 'a -> 'b -> 'c

यहां जो कुछ करी जा रही है, वह एक फ़ंक्शन को बदल रही है जो दो समझौतों को एक में ले जाती है जो केवल एक को वापस लेती है। आंशिक मूल्यांकन

किसी को इसकी आवश्यकता क्यों होगी?

xआरएचएस पर कहें तो केवल एक नियमित इंट नहीं है, बल्कि इसके बजाय एक जटिल संगणना, जो कुछ समय के लिए पूरी होती है, वृद्धि के लिए, दो सेकंड के लिए।

x = twoSecondsComputation(z)

तो फ़ंक्शन अब जैसा दिखता है

fun add (z:int) (y:int) : int =
    let
        val x = twoSecondsComputation(z)
    in
        x + y
    end;

प्रकार का add : int * int -> int

अब हम इस फ़ंक्शन को कई संख्याओं के लिए गणना करना चाहते हैं, आइए इसे मैप करते हैं

val result1 = map (fn x => add (20, x)) [3, 5, 7];

उपरोक्त परिणाम के twoSecondsComputationलिए हर बार मूल्यांकन किया जाता है। इसका मतलब है कि इस गणना के लिए 6 सेकंड लगते हैं।

मंचन और करीने के संयोजन का उपयोग करके इससे बचा जा सकता है।

fun add (z:int) : int -> int =
    let
        val x = twoSecondsComputation(z)
    in
        (fn y => x + y)
    end;

के करी रूप का add : int -> int -> int

अब एक कर सकते हैं,

val add' = add 20;
val result2 = map add' [3, 5, 7, 11, 13];

twoSecondsComputationकेवल जरूरत है एक बार मूल्यांकन किया जाना। स्केल तक, दो सेकंड को 15 मिनट, या किसी भी घंटे से बदल दें, फिर 100 नंबरों के खिलाफ एक नक्शा रखें।

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


3

करीने से लचीली फ़ंक्शन संरचना की अनुमति मिलती है।

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

var builder = curry(function(input, logger, action) {
     logger.log("Starting action");
     try {
         action(input);
         logger.log("Success!");
     }
     catch (err) {
         logger.logerror("Boo we failed..", err);
     }
});
var x = "My input.";
goGatherArgs(builder)(x); // Supplies action first, then logger somewhere.

बिल्डर वैरिएबल एक फ़ंक्शन है जो एक फ़ंक्शन देता है जो एक फ़ंक्शन देता है जो मेरा इनपुट लेता है जो मेरा काम करता है। यह एक सरल उपयोगी उदाहरण है और दृष्टि में कोई वस्तु नहीं है।


2

जब आप किसी फ़ंक्शन के लिए सभी तर्क नहीं रखते हैं, तो करी एक फायदा है। यदि आप पूरी तरह से फ़ंक्शन का मूल्यांकन करते हैं, तो कोई महत्वपूर्ण अंतर नहीं है।

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

उदाहरण के लिए, जब कार्यों को तर्क के रूप में लेते हैं, तो आप अक्सर उन स्थितियों में खुद को पाएंगे जहां आपको "3 से इनपुट जोड़ें" या "इनपुट की तुलना चर v से करें" जैसे कार्यों की आवश्यकता होती है। करीने से, ये कार्य आसानी से लिखे गए हैं: add 3और (== v)। करी के बिना, आपको लैम्ब्डा के भावों का उपयोग करना होगा: x => add 3 xऔर x => x == v। लैम्ब्डा एक्सप्रेशन दो बार लंबे होते हैं, और एक छोटी मात्रा में काम करने से संबंधित होता है एक नाम लेने के अलावा xअगर वहाँ पहले से ही एक xगुंजाइश है।

करी पर आधारित भाषाओं का एक लाभ यह है कि, जब कार्यों के लिए सामान्य कोड लिखते हैं, तो आप मापदंडों की संख्या के आधार पर सैकड़ों वेरिएंट के साथ समाप्त नहीं होते हैं। उदाहरण के लिए, C # में, एक 'करी' विधि में Func <R>, Func <A, R>, Func <A1, A2, R>, Func <A1, A2, A3, R>, और इसके आगे के वेरिएंट की आवश्यकता होगी। सदैव। हास्केल में, एक फंक <A1, A2, R> के बराबर एक फंक <Tuple <A1, A2>, R> या एक Func <A1, Func <A2, R >> (और एक Func <>>) की तरह है। एक फंक <यूनिट, आर>) की तरह अधिक है, इसलिए सभी वेरिएंट एकल फंक <ए, आर> केस के अनुरूप हैं।


2

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


1
वास्तव में चीजों को अधिक कुशल बनाने के लिए बहुत अधिक स्टैक फ्रेम बनाने की आवश्यकता कैसे होती है?
मेसन व्हीलर

1
@MasonWheeler: मुझे नहीं पता होगा, जैसा कि मैंने कहा कि मैं कार्यात्मक भाषाओं का विशेषज्ञ नहीं हूं या विशेष रूप से करी। मैंने इस समुदाय को विशेष रूप से विकी के कारण लेबल किया था।
जोएल एथरटन

4
@MasonWheeler आपके पास इस उत्तर के वाक्यांश लेखन का एक बिंदु है, लेकिन मुझे अंदर जाने दें और कहें कि वास्तव में बनाए गए स्टैक फ्रेम की मात्रा कार्यान्वयन पर बहुत कुछ निर्भर करती है। उदाहरण के लिए, स्पिनलेस टैगलेस जी मशीन (एसटीजी; जिस तरह से जीएचसी लागू होता हैस्केल) वास्तविक मूल्यांकन में देरी करता है जब तक कि यह सभी को जमा नहीं करता (या कम से कम जितने की आवश्यकता होती है) तर्क। मुझे याद नहीं आ रहा है कि क्या यह सभी कार्यों के लिए या केवल निर्माणकर्ताओं के लिए किया गया है, लेकिन मुझे लगता है कि यह अधिकांश कार्यों के लिए संभव होना चाहिए। (फिर, "स्टैक फ्रेम" की अवधारणा वास्तव में एसटीजी पर लागू नहीं होती है।)

1

किसी फ़ंक्शन को वापस करने की क्षमता पर करी महत्वपूर्ण रूप से (निश्चित रूप से भी) निर्भर करती है।

इस (विचाराधीन) छद्म कोड पर विचार करें।

var f = (m, x, b) => ... कुछ वापस करें ...

मान लें कि तीन तर्कों के साथ कॉलिंग एफ एक फ़ंक्शन देता है।

var g = f (0, 1); // यह 0 और 1 (m और x) से जुड़ा एक फ़ंक्शन देता है जो एक और तर्क (b) को स्वीकार करता है।

var y = g (42); // मी और एक्स के लिए 0 और 1 का उपयोग करते हुए, लापता तीसरे तर्क के साथ जी का आह्वान करें

कि आप आंशिक रूप से तर्कों को लागू कर सकते हैं और फिर से उपयोग करने योग्य फ़ंक्शन प्राप्त कर सकते हैं (उन तर्कों के लिए जो आपने आपूर्ति की थी) काफी उपयोगी है (और DRY)।

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