Functor
हास्केल में प्रकार वर्ग पर विचार करें , जहां f
एक उच्च-प्रकार का प्रकार चर है:
class Functor f where
fmap :: (a -> b) -> f a -> f b
क्या इस प्रकार के हस्ताक्षर का कहना है कि FMAP एक के प्रकार के पैरामीटर बदल जाता है f
से a
करने के लिए b
, लेकिन पत्ते f
के रूप में यह किया गया था। इसलिए यदि आप fmap
एक सूची का उपयोग करते हैं, तो आपको एक सूची मिलती है, यदि आप इसका उपयोग किसी ऐसे पार्सर से करते हैं जो आपको पार्सर मिलता है, और इसी तरह। और ये स्थिर , संकलन-समय की गारंटी हैं।
मुझे F # नहीं पता है, लेकिन आइए विचार करें कि क्या होता है अगर हम Functor
विरासत या जेनरिक के साथ जावा या सी # जैसी भाषा में अमूर्तता को व्यक्त करने की कोशिश करते हैं , लेकिन कोई उच्चतर किस्म का जेनरिक नहीं। पहली कोशिश:
interface Functor<A> {
Functor<B> map(Function<A, B> f);
}
इस पहली कोशिश के साथ समस्या यह है कि इंटरफ़ेस के कार्यान्वयन को लागू करने वाले किसी भी वर्ग को वापस करने की अनुमति है Functor
। कोई व्यक्ति लिख सकता है कि FunnyList<A> implements Functor<A>
किसकी map
विधि एक अलग तरह का संग्रह लौटाती है, या कुछ और भी जो संग्रह बिल्कुल नहीं है, लेकिन फिर भी एक है Functor
। इसके अलावा, जब आप map
विधि का उपयोग करते हैं तो आप परिणाम पर किसी भी उप-विशिष्ट तरीके को लागू नहीं कर सकते हैं जब तक कि आप इसे उस प्रकार से डाउनकास्ट नहीं करते हैं जो आप उम्मीद कर रहे हैं। इसलिए हमें दो समस्याएं हैं:
- प्रकार प्रणाली हमें अपरिवर्तनीय व्यक्त करने की अनुमति नहीं देती है कि
map
विधि हमेशा समान होती हैFunctor
रिसीवर के रूप में उपवर्ग ।
- इसलिए,
Functor
परिणाम के आधार पर गैर- विधि को लागू करने के लिए कोई सांख्यिकीय प्रकार-सुरक्षित तरीका नहीं है map
।
अन्य, अधिक जटिल तरीके हैं जिन्हें आप आज़मा सकते हैं, लेकिन उनमें से कोई भी वास्तव में काम नहीं करता है। उदाहरण के लिए, आप Functor
परिणाम प्रकार को प्रतिबंधित करने वाले उप-प्रकारों को परिभाषित करके पहले प्रयास को बढ़ाने का प्रयास कर सकते हैं :
interface Collection<A> extends Functor<A> {
Collection<B> map(Function<A, B> f);
}
interface List<A> extends Collection<A> {
List<B> map(Function<A, B> f);
}
interface Set<A> extends Collection<A> {
Set<B> map(Function<A, B> f);
}
interface Parser<A> extends Functor<A> {
Parser<B> map(Function<A, B> f);
}
यह उन संकरा इंटरफेस के कार्यान्वयनकर्ताओं Functor
को map
विधि से गलत प्रकार को वापस करने से रोकने में मदद करता है , लेकिन चूंकि Functor
आपके पास कितने कार्यान्वयन हो सकते हैं, इसकी कोई सीमा नहीं है, आपको कितनी संकरा इंटरफेस की आवश्यकता होगी, इसकी कोई सीमा नहीं है।
( संपादित करें: और ध्यान दें कि यह केवल इसलिए काम करता है क्योंकि Functor<B>
परिणाम प्रकार के रूप में प्रकट होता है, और इसलिए बच्चे के इंटरफेस इसे संकीर्ण कर सकते हैं। इसलिए AFAIK हम Monad<B>
निम्नलिखित इंटरफ़ेस के दोनों उपयोगों को संकीर्ण नहीं कर सकते हैं :
interface Monad<A> {
<B> Monad<B> flatMap(Function<? super A, ? extends Monad<? extends B>> f);
}
हास्केल में, उच्च-रैंक प्रकार चर के साथ, यह है (>>=) :: Monad m => m a -> (a -> m b) -> m b
।)
फिर भी एक और प्रयास करने के लिए पुनरावर्ती जेनेरिक का उपयोग करना है और इंटरफ़ेस के परिणाम प्रकार को उपप्रकार को केवल उपप्रकार तक सीमित रखना है। खिलौना उदाहरण:
interface Semigroup<T extends Semigroup<T>> {
T append(T arg);
}
class Foo implements Semigroup<Foo> {
Foo append(Foo arg);
}
class Bar implements Semigroup<Bar> {
Semigroup<Bar> append(Semigroup<Bar> arg);
Semigroup<Foo> append(Bar arg);
Semigroup append(Bar arg);
Foo append(Bar arg);
}
लेकिन इस तरह की तकनीक (जो आपके रन-ऑफ-द-मिल OOP डेवलपर के लिए समान है, आपके रन-ऑफ-द-मिल कार्यात्मक डेवलपर के लिए भी) अभी भी वांछित Functor
बाधा व्यक्त नहीं कर सकती है :
interface Functor<FA extends Functor<FA, A>, A> {
<FB extends Functor<FB, B>, B> FB map(Function<A, B> f);
}
समस्या यहाँ यह सीमित नहीं करता है FB
एक ही है करने के लिए F
के रूप में FA
है, ताकि जब आप एक प्रकार घोषणा करते हैं कि List<A> implements Functor<List<A>, A>
, map
विधि कर सकते हैं अभी भी एक वापसी NotAList<B> implements Functor<NotAList<B>, B>
।
अंतिम प्रयास, जावा में, कच्चे प्रकार (अनमीट्रीड कंटेनर) का उपयोग करके:
interface FunctorStrategy<F> {
F map(Function f, F arg);
}
यहाँ F
बस List
या जैसे अनपेक्षित तरीके से त्वरित मिलेगा Map
। यह गारंटी देता है कि एक FunctorStrategy<List>
ही वापस आ सकता हैList
सूची के तत्व प्रकारों को ट्रैक करने के लिए आप हैं-आपने प्रकार चर का उपयोग छोड़ दिया है।
यहाँ समस्या का दिल यह है कि जावा और सी # जैसी भाषाएं मापदंडों को टाइप करने की अनुमति नहीं देती हैं। जावा में, यदि T
एक प्रकार का चर है, तो आप लिख सकते हैं T
और List<T>
, लेकिन नहीं T<String>
। उच्च-प्रकार के प्रकार इस प्रतिबंध को हटा देते हैं, ताकि आपके पास ऐसा कुछ हो (पूरी तरह से सोचा नहीं गया):
interface Functor<F, A> {
<B> F<B> map(Function<A, B> f);
}
class List<A> implements Functor<List, A> {
<B> List<B> map(Function<A, B> f) {
}
}
और इस बिट को विशेष रूप से संबोधित करते हुए:
(मुझे लगता है) मुझे लगता है कि myList |> List.map f
या myList |> Seq.map f |> Seq.toList
उच्च प्रकार के बजाय आपको बस लिखने की अनुमति मिलती है myList |> map f
और यह वापस आ जाएगी List
। यह बहुत अच्छा है (यह मानते हुए कि यह सही है), लेकिन एक तरह से छोटा लगता है? (और यह केवल समारोह अतिभारित करने की अनुमति देकर नहीं किया जा सकता है?) मैं आमतौर पर Seq
किसी भी तरह से परिवर्तित करता हूं और फिर मैं जो कुछ भी चाहता हूं उसे बाद में परिवर्तित कर सकता हूं।
कई भाषाएं हैं जो map
फ़ंक्शन के विचार को इस तरह से सामान्य करती हैं, जैसे कि यह मॉडलिंग करके, दिल से, मानचित्रण दृश्यों के बारे में है। आपकी यह टिप्पणी उस भावना में है: यदि आपके पास एक ऐसा प्रकार है जो Seq
पुन: उपयोग करने के लिए "मुफ्त में" मानचित्र संचालन प्राप्त करता है Seq.map
।
हास्केल में, हालांकि, Functor
वर्ग उससे अधिक सामान्य है; यह दृश्यों की धारणा से बंधा नहीं है। आप उन fmap
प्रकारों के लिए कार्यान्वित कर सकते हैं जिनमें अनुक्रमों के लिए कोई अच्छा मानचित्रण नहीं है, जैसे कि IO
क्रियाएं, पार्सर कॉम्बिनेटर, फ़ंक्शन, आदि।:
instance Functor IO where
fmap f action =
do x <- action
return (f x)
newtype Function a b = Function (a -> b)
instance Functor (Function a) where
fmap f (Function g) = Function (f . g)
"मैपिंग" की अवधारणा वास्तव में अनुक्रम से जुड़ी नहीं है। फ़नकार कानूनों को समझना सबसे अच्छा है:
(1) fmap id xs == xs
(2) fmap f (fmap g xs) = fmap (f . g) xs
बहुत अनौपचारिक रूप से:
- पहला कानून कहता है कि किसी पहचान / नूप फ़ंक्शन के साथ मैपिंग कुछ भी नहीं करने के समान है।
- दूसरा कानून कहता है कि कोई भी परिणाम जो आप दो बार मैप करके पैदा कर सकते हैं, आप एक बार मैप करके भी उत्पादन कर सकते हैं।
यही कारण है कि आप fmap
प्रकार को संरक्षित करना चाहते हैं - क्योंकि जैसे ही आपको ऐसे map
ऑपरेशन मिलते हैं जो एक अलग परिणाम प्रकार का उत्पादन करते हैं, यह इस तरह की गारंटी देने के लिए बहुत, बहुत कठिन हो जाता है।