function उच्च-क्रम के कार्यों के कुछ दिलचस्प उपयोग क्या हैं?




haskell functional-programming (12)

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

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

पर्ल में कार्यात्मक प्रोग्रामिंग दृष्टिकोण के उदाहरण हर रोज़ प्रोग्रामिंग में दिखाई देते हैं, विशेष रूप से map और grep :

@ARGV    = map { /\.gz$/ ? "gzip -dc < $_ |" : $_ } @ARGV;

@unempty = grep { defined && length } @many;

चूंकि sort भी एक क्लोजर को स्वीकार करता है, map/sort/map पैटर्न सुपर कॉमन है:

@txtfiles = map { $_->[1] }
            sort { 
                    $b->[0]  <=>     $a->[0]
                              ||
                 lc $a->[1]  cmp  lc $b->[1]
                              ||
                    $b->[1]  cmp     $a->[1]
            }
            map  { -s => $_ } 
            grep { -f && -T }
            glob("/etc/*");

या

@sorted_lines = map { $_->[0] }
                sort {
                     $a->[4] <=> $b->[4] 
                             ||
                    $a->[-1] cmp $b->[-1]
                             ||
                     $a->[3] <=> $b->[3]
                             ||
                     ...
                }
                map { [$_ => reverse split /:/] } @lines;

reduce कार्य बिना हैकिंग के सूची हैकिंग को आसान बनाता है:

$sum = reduce { $a + $b } @numbers;

$max = reduce { $a > $b ? $a : $b } $MININT, @numbers;

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

try {
   something();
} catch {
   oh_drat();
};

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

पर्ल 5 में कर्विंग बिल्ट-इन नहीं है, हालांकि इसके लिए एक मॉड्यूल है। पर्ल 6, हालांकि, इसमें करी और प्रथम श्रेणी की निरंतरता है, जिसमें बहुत अधिक है, और बहुत कुछ है।

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

क्या आप ऐसे दिलचस्प कार्यों के उदाहरण जानते हैं?

हो सकता है कि फ़ंक्शंस, जो फ़ंक्शंस लौटाए, फ़ंक्शंस, जो फ़ंक्शंस की लिस्ट (?), आदि।

मैं हास्केल में उदाहरणों की सराहना करूंगा, जो कि मैं वर्तमान में सीख रहा हूं। :)


मैं उच्च-क्रम संस्मरण का विशेष प्रशंसक हूं:

memo :: HasTrie t => (t -> a) -> (t -> a)

(किसी भी फ़ंक्शन को देखते हुए, उस फ़ंक्शन के एक संस्मरणित संस्करण को वापस लौटाएं। इस तथ्य से कि फ़ंक्शन के तर्कों को एक ट्राइक में एन्कोड किया जा सकता है।)

यह http://hackage.haskell.org/package/MemoTrie


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

f :: A -> B -> C

... (->) को सही-सहकार के रूप में पढ़ा जा सकता है, यह दिखाते हुए कि यह वास्तव में एक उच्च-क्रम फ़ंक्शन है जो B -> C : का प्रकार देता B -> C

f :: A -> (B -> C)

दो तर्कों का एक गैर-करीबी कार्य इसके बजाय एक प्रकार होगा:

f' :: (A, B) -> C

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


यहाँ एक छोटा पैराफ़्रेस्ड code स्निपेट दिया गया है:

rays :: ChessPieceType -> [[(Int, Int)]]
rays Bishop = do
  dx <- [1, -1]
  dy <- [1, -1]
  return $ iterate (addPos (dx, dy)) (dx, dy)
...  -- Other piece types

-- takeUntilIncluding is an inclusive version of takeUntil
takeUntilIncluding :: (a -> Bool) -> [a] -> [a]

possibleMoves board piece = do
  relRay <- rays (pieceType piece)
  let ray = map (addPos src) relRay
  takeUntilIncluding (not . isNothing . pieceAt board)
    (takeWhile notBlocked ray)
  where
    notBlocked pos =
      inBoard pos &&
      all isOtherSide (pieceAt board pos)
    isOtherSide = (/= pieceSide piece) . pieceSide

यह कई "उच्च क्रम" कार्यों का उपयोग करता है:

iterate :: (a -> a) -> a -> [a]
takeUntilIncluding  -- not a standard function
takeWhile :: (a -> Bool) -> [a] -> [a]
all :: (a -> Bool) -> [a] -> Bool
map :: (a -> b) -> [a] -> [b]
(.) :: (b -> c) -> (a -> b) -> a -> c
(>>=) :: Monad m => m a -> (a -> m b) -> m b

(.) है . ऑपरेटर, और (>>=) do -notation "लाइन ब्रेक ऑपरेटर" है।

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


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

statFuncs :: [ [Double] -> Double ]
statFuncs = [minimum, maximum, mean, median, mode, stddev]

runWith funcs samples = map ($samples) funcs

यहाँ सभी प्रकार के उच्च क्रम की अच्छाई चल रही है, जिनमें से कुछ का अन्य उत्तर में उल्लेख किया गया है। लेकिन मैं '$' फ़ंक्शन को इंगित करना चाहता हूं। जब मैंने पहली बार '$' का यह प्रयोग देखा, तो मैं उड़ गया था। इससे पहले कि मैं इसे कोष्ठक के लिए एक सुविधाजनक प्रतिस्थापन के रूप में बहुत उपयोगी नहीं माना जाता था ... लेकिन यह लगभग जादुई था ...


मैं वास्तव में शक्ति को महसूस करना शुरू कर दिया जब मैंने सीखा कि एक फ़ंक्शन एक डेटा संरचना का हिस्सा हो सकता है। यहाँ एक "उपभोक्ता मोनाड" है (टेक्नोब्लेबल: फ्री मोनाड ओवर (i ->) )।

data Coro i a
    = Return a
    | Consume (i -> Coro i a)

तो एक Coro या तो तुरंत एक मूल्य प्राप्त कर सकता है, या कुछ इनपुट के आधार पर एक और कोरो हो सकता है। उदाहरण के लिए, यह एक Coro Int Int :

Consume $ \x -> Consume $ \y -> Consume $ \z -> Return (x+y+z)

यह तीन पूर्णांक इनपुट्स का उपभोग करता है और उनकी राशि लौटाता है। आप यह भी इनपुट के अनुसार अलग तरह से व्यवहार कर सकते हैं:

sumStream :: Coro Int Int
sumStream = Consume (go 0)
    where
    go accum 0 = Return accum
    go accum n = Consume (\x -> go (accum+x) (n-1))

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

डेटा संरचनाओं में कार्य एक बहुत ही शक्तिशाली उपकरण है जो हस्केल करने से पहले मेरी शब्दावली का हिस्सा नहीं था।


पेपर देखें 'पार्स करने के लिए भी उच्च-आदेश के कार्य या कोई भी कभी भी छठे-ऑर्डर फ़ंक्शन का उपयोग क्यों करना चाहेगा?' क्रिस ओकासाकी द्वारा। यह एमएल का उपयोग करते हुए लिखा गया है, लेकिन विचार हास्केल पर समान रूप से लागू होते हैं।


मार्टीन एस्कार्डो एक उच्च-क्रम फ़ंक्शन का एक दिलचस्प उदाहरण प्रदान करता है:

equal :: ((Integer -> Bool) -> Int) -> ((Integer -> Bool) -> Int) -> Bool

दो फंक्शन्स f, g :: (Integer -> Bool) -> Int को देखते हुए, तब f और g समान (समान रूप से) बराबर equal fg तय करता है, भले ही f और g में परिमित डोमेन न हो। वास्तव में, कोडोमैन, Int , को किसी भी प्रकार से एक निर्णायक समानता के साथ बदला जा सकता है।

Escardó का कोड कोड हास्केल में लिखा गया है, लेकिन उसी एल्गोरिथ्म को किसी भी कार्यात्मक भाषा में काम करना चाहिए।

आप वही तकनीकों का उपयोग कर सकते हैं जो Escardó किसी भी निरंतर फ़ंक्शन के निश्चित इंटीग्रल को मनमाने ढंग से सटीक गणना करने के लिए वर्णन करता है।


खैर, आप ध्यान दें कि हास्केल के पास छोरों के लिए कोई वाक्यविन्यास नहीं है? कोई while या do या के for । क्योंकि ये सभी केवल उच्च-क्रम के कार्य हैं:

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

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

 filter :: (a -> Bool) -> [a] -> [a]

 unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

 iterate :: (a -> a) -> a -> [a]

उच्च-क्रम वाले कार्य नियंत्रण संरचनाओं के लिए भाषा में वाक्य रचना में पके हुए की आवश्यकता को प्रतिस्थापित करते हैं, जिसका अर्थ है कि बहुत से हरस्केल कार्यक्रम इन कार्यों का उपयोग करता है - उन्हें काफी उपयोगी बनाता है!

वे अच्छे अमूर्तता की ओर पहला कदम हैं क्योंकि अब हम कस्टम व्यवहार को एक सामान्य उद्देश्य कंकाल फ़ंक्शन में प्लग कर सकते हैं।

विशेष रूप से, monads केवल संभव हैं क्योंकि हम कार्यक्रमों को बनाने के लिए एक साथ श्रृंखला बना सकते हैं, और कार्यों में हेरफेर कर सकते हैं।

तथ्य यह है, जब यह फर्स्ट-ऑर्डर होता है तो जीवन बहुत उबाऊ होता है। एक बार क्रमबद्ध होने के बाद ही प्रोग्रामिंग दिलचस्प हो जाती है।


जोएल स्पोलस्की ने एक प्रसिद्ध निबंध लिखा था जिसमें बताया गया था कि कैसे जावास्क्रिप्ट के उच्च क्रम वाले कार्यों का उपयोग करके Map-Reduce काम करता है। यह प्रश्न पूछने वाले किसी भी व्यक्ति को अवश्य पढ़ना चाहिए।


OO प्रोग्रामिंग में उपयोग की जाने वाली कई तकनीकें उच्च क्रम के कार्यों की कमी के लिए वर्कअराउंड हैं।

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

strategy पैटर्न एक ऐसी योजना का एक और उदाहरण है जो अक्सर वस्तुओं को तर्क के रूप में पारित करती है जो वास्तव में इरादा है, कार्य करता है।

इसी तरह निर्भरता इंजेक्शन में अक्सर कार्यों के लिए एक प्रॉक्सी पास करने के लिए कुछ क्लूनी स्कीम शामिल होती है जब अक्सर तर्कों के बाद सीधे फ़ंक्शन में पास करना बेहतर होगा।

तो मेरा जवाब यह होगा कि उच्च-क्रम के कार्यों का उपयोग अक्सर उसी प्रकार के कार्यों को करने के लिए किया जाता है जो OO प्रोग्रामर करते हैं, लेकिन सीधे, और बहुत कम बॉयलरप्लेट के साथ।


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

मेरा हास्केल बल्कि कठोर है इसलिए मैं आपको आसानी से हास्केल का उदाहरण नहीं दे सकता, लेकिन यहाँ एक सरल क्लोजर उदाहरण दिया गया है जो उम्मीद है कि अवधारणा को व्यक्त करता है:

(defn make-object [initial-value]
  (let [data (atom {:value initial-value})]
      (fn [op & args]
        (case op 
          :set (swap! data assoc :value (first args))
          :get (:value @data)))))

उपयोग:

(def a (make-object 10))

(a :get)
=> 10

(a :set 40)

(a :get)
=> 40

हास्केल में समान सिद्धांत काम करेगा (सिवाय इसके कि आपको एक नया फ़ंक्शन वापस करने के लिए सेट ऑपरेशन को बदलने की आवश्यकता होगी क्योंकि हास्केल बहुत खराब है)





higher-order-functions