क्या कोई हास्केल में चल समारोह की व्याख्या कर सकता है?


99

मैं कोशिश कर रहा हूं और इस traverseसमारोह को विफल कर रहा हूं Data.Traversable। मैं इसकी बात नहीं देख पा रहा हूं। जब से मैं एक अनिवार्य पृष्ठभूमि से आया हूं, क्या कोई मुझे एक अनिवार्य लूप के संदर्भ में समझा सकता है? छद्म कोड की बहुत सराहना की जाएगी। धन्यवाद।


1
लेख Iterator पैटर्न का सार सहायक हो सकता है क्योंकि यह कदम से कदम की धारणा का निर्माण करता है। कुछ उन्नत अवधारणाएँ हालांकि मौजूद हैं
जैकी

जवाबों:


121

traverseयह वैसा ही है fmap, सिवाय इसके कि यह आपको डेटा संरचना के पुनर्निर्माण के दौरान भी प्रभाव चलाने की अनुमति देता है।

Data.Traversableदस्तावेज़ीकरण से उदाहरण पर एक नज़र डालें ।

 data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)

का Functorउदाहरण Treeहोगा:

instance Functor Tree where
  fmap f Empty        = Empty
  fmap f (Leaf x)     = Leaf (f x)
  fmap f (Node l k r) = Node (fmap f l) (f k) (fmap f r)

यह पूरे पेड़ का पुनर्निर्माण करता है, fहर मूल्य पर लागू होता है।

instance Traversable Tree where
    traverse f Empty        = pure Empty
    traverse f (Leaf x)     = Leaf <$> f x
    traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r

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

ऐतिहासिक कारणों से, Traversableवर्ग में traverseबुलाया का एक राक्षसी संस्करण भी है mapM। सभी इरादों और उद्देश्यों mapMके लिए समान है traverse- यह एक अलग विधि के रूप में मौजूद है क्योंकि Applicativeकेवल बाद में एक सुपरक्लास बन गया Monad

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

Node.prototype.traverse = function (f) {
  return new Node(this.l.traverse(f), f(this.k), this.r.traverse(f));
}

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


11
'प्रभाव' शब्द का क्या अर्थ है?
लापता

24
@missingfaktor: इसका अर्थ है कि संरचनात्मक जानकारी Functor, वह हिस्सा जो पैरामीट्रिक नहीं है। में राज्य मूल्य Stateमें, असफलता Maybeऔर Either, तत्वों में की संख्या []है, और में पाठ्यक्रम मनमाने ढंग से बाहरी साइड इफेक्ट के IO। मैं इसके लिए एक सामान्य शब्द के रूप में परवाह नहीं करता हूं (जैसे Monoid"खाली" और "एपेंड" का उपयोग करने वाले फ़ंक्शन, अवधारणा शब्द पहले की तुलना में अधिक सामान्य है), लेकिन यह काफी सामान्य है और उद्देश्य को अच्छी तरह से पूरा करता है।
सीए मैककैन

@CA मैककैन: समझ गया। जवाब के लिए धन्यवाद!
14 अक्टूबर

1
"मुझे पूरा यकीन है कि आप ऐसा नहीं करने वाले हैं [...]।" निश्चित रूप से नहीं - यह apपिछले परिणामों पर निर्भर के प्रभावों को बनाने के समान ही बुरा होगा । मैंने उस टिप्पणी को उसी के अनुसार पुन: प्रस्तुत किया है।
डुप्लोड

2
"आवेदक लगभग वैसा ही है जैसा कि वह है, सिवाय इसके कि पिछले परिणामों पर निर्भर नहीं किया जा सकता है।" ... इस लाइन के साथ मेरे लिए बहुत सारी सामग्री क्लिक हुई, धन्यवाद!
अगम

58

traverseचीजों को "अंदर" चीजों Traversableमें बदल देता है , एक ऐसा कार्य दिया Traversableजाता है Applicative, Applicativeजो चीजों से बाहर हो जाता है।

के Maybeरूप में उपयोग करते हैं Applicativeऔर के रूप में सूची Traversable। पहले हमें परिवर्तन समारोह की आवश्यकता है:

half x = if even x then Just (x `div` 2) else Nothing

इसलिए यदि कोई संख्या सम है, तो हम उसका आधा (अंदर Just), दूसरे को प्राप्त करते हैं Nothing। यदि सब कुछ "अच्छी तरह से" चला जाता है, तो यह इस तरह दिखता है:

traverse half [2,4..10]
--Just [1,2,3,4,5]

परंतु...

traverse half [1..10]
-- Nothing

कारण यह है कि <*>फ़ंक्शन का उपयोग परिणाम बनाने के लिए किया जाता है, और जब कोई तर्क होता है Nothing, तो हम Nothingवापस आ जाते हैं।

एक और उदाहरण:

rep x = replicate x x

यह फ़ंक्शन xसामग्री के साथ लंबाई की एक सूची बनाता है x, जैसे rep 3= [3,3,3]। का परिणाम क्या है traverse rep [1..3]?

हमें आंशिक परिणाम मिलते हैं [1], [2,2]और [3,3,3]उपयोग करते हैं rep। अब सूचियों का शब्दार्थ Applicatives"सभी संयोजनों को लेना" है, उदाहरण के (+) <$> [10,20] <*> [3,4]लिए [13,14,23,24]

"सभी संयोजन" [1]और [2,2]दो बार हैं [1,2]। दो बार के सभी संयोजन [1,2]और [3,3,3]छह बार हैं [1,2,3]। तो हमारे पास:

traverse rep [1..3]
--[[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]

1
आप अंतिम परिणाम मुझे की याद दिलाता है यह
hugomg

3
@ मिस्सिंगो: हाँ, वे चूक गएfac n = length $ traverse rep [1..n]
लांडे

1
वास्तव में, इसका "सूची-एन्कोडिंग-प्रोग्रामर" के तहत (लेकिन सूची की समझ का उपयोग करके)। वह वेबसाइट व्यापक है :)
hugomg

1
@ मिस्सिंगो: हम्म, यह बिल्कुल समान नहीं है ... दोनों ही सूची में मोनड के कार्टेशियन उत्पाद व्यवहार पर भरोसा कर रहे हैं, लेकिन साइट एक समय में केवल दो का उपयोग करती है, इसलिए यह liftA2 (,)अधिक सामान्य रूप का उपयोग करने से अधिक पसंद है traverse
सीए मैक्कन

41

मुझे लगता है कि इसे समझना आसान है sequenceA, जैसा traverseकि इस प्रकार परिभाषित किया जा सकता है।

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f

sequenceA एक संरचना के तत्वों को बाएं से दाएं तक एक साथ अनुक्रमित करता है, एक संरचना को उसी आकार के साथ लौटाता है जिसमें परिणाम होते हैं।

sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
sequenceA = traverse id

आप sequenceAदो फंक्शनलर्स के क्रम को उलटने के बारे में भी सोच सकते हैं , जैसे कि कार्रवाई की सूची से परिणामों की सूची को लौटाने वाली कार्रवाई में।

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

आप इसकी तुलना भी कर सकते हैं Foldable, जो संबंधित फ़ंक्शन को परिभाषित करता है traverse_

traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()

आप देख सकते हैं ताकि के बीच मुख्य अंतर Foldableऔर Traversableहै कि बाद के हैं, जबकि पूर्व कुछ अन्य मूल्य में परिणाम मोड़ें करने की आवश्यकता है आप संरचना के आकार को बनाए रखने के लिए अनुमति देता है।


इसके उपयोग का एक सरल उदाहरण ट्रैवर्सेबल संरचना के IOरूप में और आवेदक के रूप में एक सूची का उपयोग कर रहा है :

λ> import Data.Traversable
λ> let qs = ["name", "quest", "favorite color"]
λ> traverse (\thing -> putStrLn ("What is your " ++ thing ++ "?") *> getLine) qs
What is your name?
Sir Lancelot
What is your quest?
to seek the holy grail
What is your favorite color?
blue
["Sir Lancelot","to seek the holy grail","blue"]

हालांकि यह उदाहरण बल्कि अस्पष्ट है, traverseअन्य प्रकार के कंटेनरों पर, या अन्य ऐप्लिकेशंस का उपयोग करने पर चीजें अधिक दिलचस्प हो जाती हैं।


तो पारगमन बस नक्शे का एक सामान्य रूप है? वास्तव में, sequenceA . fmapसूचियों के बराबर sequence . mapनहीं है?
रस्केल

'अनुक्रमण साइड इफेक्ट्स' से आपका क्या तात्पर्य है? आपके उत्तर में thought साइड इफेक्ट ’क्या है - मैंने अभी सोचा था कि साइड इफेक्ट केवल मुनियों में ही संभव हैं। सादर।
मारेक

1
@ मारेक "मुझे लगा कि साइड इफेक्ट्स केवल मोनाड्स में संभव हैं" - कनेक्शन इससे बहुत कम है: (1) साइड इफेक्ट्स को व्यक्त करने के लिए IO टाइप का उपयोग किया जा सकता है; (२) IOएक मोनाड होता है, जो बहुत सुविधाजनक होता है। मोनाड्स अनिवार्य रूप से साइड इफेक्ट्स से जुड़े नहीं हैं। यह भी ध्यान दिया जाना चाहिए कि "प्रभाव" का एक अर्थ है जो अपने सामान्य अर्थों में "दुष्प्रभाव" से व्यापक है - एक जिसमें शुद्ध संगणना शामिल है। इस अंतिम बिंदु पर, यह भी देखें कि वास्तव में "प्रभावी" का क्या अर्थ है
1

(वैसे, @hammar, मैंने ऊपर दिए गए टिप्पणी में उल्लिखित कारणों के कारण इस उत्तर में "दुष्प्रभाव" को "प्रभाव" में बदलने की स्वतंत्रता ले ली।)
dllode

17

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

एक डेटाबेस में उपयोगकर्ता आईडी का प्रतिनिधित्व करने वाले पूर्णांक की एक सूची की कल्पना करें [1, 2, 3]:। यदि आप fmapइन उपयोगकर्ता IDs को उपयोगकर्ता नाम के लिए चाहते हैं , तो आप एक पारंपरिक का उपयोग नहीं कर सकते हैं fmap, क्योंकि फ़ंक्शन के अंदर आपको उपयोगकर्ता नाम पढ़ने के लिए डेटाबेस तक पहुंचने की आवश्यकता है (जिसमें एक प्रभाव की आवश्यकता है - इस मामले में, IOसनक का उपयोग करके )।

का हस्ताक्षर traverseहै:

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

साथ में traverse , आप प्रभाव कर सकते हैं, इसलिए, उपयोगकर्ता आईडी को उपयोगकर्ता नाम मैप करने के लिए आपका कोड इस तरह दिखता है:

mapUserIDsToUsernames :: (Num -> IO String) -> [Num] -> IO [String]
mapUserIDsToUsernames fn ids = traverse fn ids

वहाँ भी एक समारोह कहा जाता है mapM :

mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)

के किसी भी उपयोग के mapMसाथ प्रतिस्थापित किया जा सकता हैtraverse , लेकिन दूसरे तरीके से नहीं। mapMकेवल मठों के लिए काम करता है, जबकि traverseअधिक सामान्य है।

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



7

traverse है पाश। इसका कार्यान्वयन ट्रेस किए जाने वाले डेटा संरचना पर निर्भर करता है। यह एक सूची हो सकती है, वृक्ष Maybe,Seq (uence), या कुछ भी एक के लिए लूप या पुनरावर्ती क्रिया की तरह कुछ के माध्यम से चल जा रहा है की एक सामान्य तरीके है। एक सरणी के लिए एक लूप होगा, एक सूची एक लूप, एक लूप, एक पेड़ या तो कुछ पुनरावर्ती या थोड़ी देर के लूप के साथ एक संयोजन होगा; लेकिन कार्यात्मक भाषाओं में आपको इन बोझिल लूप आदेशों की आवश्यकता नहीं है: आप लूप के आंतरिक भाग (एक फ़ंक्शन के आकार में) को डेटा संरचना के साथ अधिक सीधे तरीके से और कम क्रिया में जोड़ते हैं।

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

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