2010-10-15 5 views
11

मेरे पास एक पॉलिमॉर्फिक फ़ंक्शन है जैसे:हास्केल में विज्ञापन-प्रसार पॉलीमोर्फिज्म को लागू करने का सबसे अच्छा तरीका?

convert :: (Show a) => a -> String 
convert = " [label=" ++ (show a) ++ "]" 

लेकिन कभी-कभी मैं इसे डेटा पास करना चाहता हूं। मैप और कुछ और फैंसी कुंजी मान रूपांतरण करें। मुझे पता है कि मैं यहाँ पैटर्न पैटर्न नहीं कर सकता क्योंकि डेटा.मैप एक अमूर्त डेटा प्रकार है (this similar SO question के अनुसार), लेकिन मैं इस अंत में गार्ड का उपयोग करने में असफल रहा हूं, और मुझे यकीन नहीं है कि व्यूपटरर्न यहां मदद करेंगे (और होगा बल्कि पोर्टेबिलिटी के लिए उनसे बचें)।

यह वही है जो मैं चाहता हूं:

import qualified Data.Map as M 

convert :: (Show a) => a -> String 
convert a 
    | M.size \=0 = processMap2FancyKVString a -- Heres a Data.Map 
    | otherwise = " [label=" ++ (show a) ++ "]" -- Probably a string 

लेकिन यह काम नहीं करता है क्योंकि M.size डेटा के अलावा कुछ भी नहीं ले सकता है। मैप।

विशेष रूप से, मैं ग्राफविज़ आउटपुट में रंगों और किनारों के अन्य विशेषताओं को संभालने के लिए sl utility function in the Functional Graph Library को संशोधित करने का प्रयास कर रहा हूं।

अद्यतन

मेरी इच्छा है कि मैं इस प्रश्न के लिए टॉमएमडी, एंटल एस-जेड और लुक्वी द्वारा सभी तीन उत्तरों को स्वीकार कर सकूं क्योंकि वे सभी समझ गए थे कि मैं वास्तव में क्या पूछ रहा था। मैं कहूंगा:

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

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

उत्तर

13

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

data Convertable k a = ConvMap (Map k a) | ConvOther a 
convert (ConvMap m) = ... 
convert (ConvOther o) = ... 

एक बेहतर तरीका प्रकार वर्गों का उपयोग करने, इस प्रकार convert समारोह खुला और एक्स्टेंसिबल छोड़ने जबकि inputting गैर sensical संयोजन (पूर्व से उपयोगकर्ताओं को रोकने है: ConvOther M.empty)

class (Show a) => Convertable a where 
    convert :: a -> String 

instance Convertable (M.Map k a) where 
    convert m = processMap2FancyKVString m 

newtype ConvWrapper a = CW a 
instance Convertable (ConvWrapper a) where 
    convert (CW a) = " [label=" ++ (show a) ++ "]" 

इस तरह से आप ऐसे उदाहरण प्राप्त कर सकते हैं जिन्हें आप प्रत्येक अलग-अलग डेटा प्रकार के लिए उपयोग करना चाहते हैं और जब भी कोई नई विशेषज्ञता की आवश्यकता होती है तो आप की परिभाषा को अन्य instance Convertable NewDataType where ... जोड़कर आसानी से बढ़ा सकते हैं।

कुछ लोगों newtype आवरण पर भ्रूभंग और की तरह एक उदाहरण का सुझाव दे सकता:

instance Convertable a where 
    convert ... 

लेकिन यह दृढ़ता से बहुत कम प्रोग्रामर सुविधा के लिए अतिव्यापी और अनिर्णनीय उदाहरणों एक्सटेंशन हतोत्साहित की आवश्यकता होगी।

8

आपकी समस्या वास्तव में उस प्रश्न के समान नहीं है। आपके द्वारा लिंक किए गए प्रश्न में, डेरेक थर्न के पास एक समारोह था जिसे वह जानता था ने Set a लिया, लेकिन पैटर्न-मिलान नहीं कर सका। आपके मामले में, आप एक फ़ंक्शन लिख रहे हैं जो a लेगा जिसमें Show का उदाहरण होगा; आप यह नहीं बता सकते कि आप रनटाइम पर किस प्रकार देख रहे हैं, और केवल उन कार्यों पर भरोसा कर सकते हैं जो किसी भी Show सक्षम प्रकार के लिए उपलब्ध हैं। यदि आप एक समारोह करना चाहते हैं तो विभिन्न डेटा प्रकारों के लिए अलग-अलग चीजें करें, इसे एड-होक पॉलिमॉर्फिज्म के रूप में जाना जाता है, और Show जैसे प्रकार वर्गों के साथ हास्केल में समर्थित है। (यह पैरामीट्रिक पॉलिमॉर्फिज्म के विपरीत है, जो कि जब आप head (x:_) = x जैसे फ़ंक्शन लिखते हैं, जिसमें head :: [a] -> a टाइप किया गया है; अनन्य सार्वभौमिक a इसके बजाय पैरामीट्रिक बनाता है।) तो आप जो चाहते हैं उसे करने के लिए, आपको बनाना होगा अपनी खुद की टाइप क्लास, और जब आपको इसकी आवश्यकता होती है तो उसे तुरंत चालू करें। हालांकि, यह सामान्य से थोड़ा अधिक जटिल है, क्योंकि आप Show का हिस्सा जो कुछ भी अपनी नई प्रकार की कक्षा का हिस्सा हैं, बनाना चाहते हैं। इसके लिए कुछ संभावित खतरनाक और शायद अनावश्यक रूप से शक्तिशाली जीएचसी एक्सटेंशन की आवश्यकता होती है। इसके बजाय, चीजों को सरल क्यों न करें? आप शायद उन प्रकारों का सबसेट निकाल सकते हैं जिन्हें आपको वास्तव में प्रिंट करने की आवश्यकता है। एक बार जब आप ऐसा करते हैं, आप कोड इस प्रकार लिख सकते हैं:

{-# LANGUAGE TypeSynonymInstances #-} 

module GraphvizTypeclass where 

import qualified Data.Map as M 
import Data.Map (Map) 

import Data.List (intercalate) -- For output formatting 

surround :: String -> String -> String -> String 
surround before after = (before ++) . (++ after) 

squareBrackets :: String -> String 
squareBrackets = surround "[" "]" 

quoted :: String -> String 
quoted = let replace '"' = "\\\"" 
      replace c = [c] 
     in surround "\"" "\"" . concatMap replace 

class GraphvizLabel a where 
    toGVItem :: a -> String 
    toGVLabel :: a -> String 
    toGVLabel = squareBrackets . ("label=" ++) . toGVItem 

-- We only need to print Strings, Ints, Chars, and Maps. 

instance GraphvizLabel String where 
    toGVItem = quoted 

instance GraphvizLabel Int where 
    toGVItem = quoted . show 

instance GraphvizLabel Char where 
    toGVItem = toGVItem . (: []) -- Custom behavior: no single quotes. 

instance (GraphvizLabel k, GraphvizLabel v) => GraphvizLabel (Map k v) where 
    toGVItem = let kvfn k v = ((toGVItem k ++ "=" ++ toGVItem v) :) 
       in intercalate "," . M.foldWithKey kvfn [] 
    toGVLabel = squareBrackets . toGVItem 

इस सेटअप में, सब कुछ है जो हम Graphviz के उत्पादन GraphvizLabel का एक उदाहरण है सकते हैं; toGVItem फ़ंक्शन उद्धरण चीजें, और toGVLabel पूरी चीज़ को तुरंत उपयोग के लिए स्क्वायर ब्रैकेट में रखता है। (मैंने आपके द्वारा इच्छित स्वरूपण में से कुछ को खराब कर दिया होगा, लेकिन वह हिस्सा सिर्फ एक उदाहरण है।) फिर आप घोषणा करते हैं कि GraphvizLabel का उदाहरण क्या है, और इसे किसी आइटम में कैसे चालू करें। TypeSynonymInstances ध्वज हमें instance GraphvizLabel [Char] के बजाय instance GraphvizLabel String लिखने देता है; यह हानिरहित है।

अब, अगर आप वास्तव में जरूरत एक Show उदाहरण के साथ सब कुछGraphvizLabel का एक उदाहरण के रूप में अच्छी तरह से हो सकता है, वहाँ एक रास्ता है। यदि आपको वास्तव में इसकी आवश्यकता नहीं है, तो इस कोड का उपयोग न करें! यदि आपको ऐसा करने की ज़रूरत है, तो आपको स्केली-नामित UndecidableInstances और OverlappingInstances भाषा एक्सटेंशन (और कम से कम FlexibleInstances नामित) सहन करना होगा। इसका कारण यह है कि आपको यह कहना है कि सबकुछ है जो Show सक्षम है GraphvizLabel-लेकिन यह संकलक के बारे में बताना मुश्किल है। उदाहरण के लिए, यदि आप इस कोड का उपयोग करते हैं और GHCi प्रॉम्प्ट पर toGVLabel [1,2,3] लिखते हैं, तो आपको एक त्रुटि मिलेगी, क्योंकि 1 में Num a => a टाइप किया गया है, और CharNum का उदाहरण हो सकता है! इसे काम करने के लिए आपको स्पष्ट रूप से toGVLabel ([1,2,3] :: [Int]) निर्दिष्ट करना होगा। फिर, यह शायद आपकी समस्या पर सहन करने के लिए अनावश्यक रूप से भारी मशीनरी है। इसके बजाए, यदि आप उन चीज़ों को सीमित कर सकते हैं जो आपको लगता है कि लेबल में परिवर्तित हो जाएंगे, जो बहुत संभावना है, तो आप केवल उन चीज़ों को निर्दिष्ट कर सकते हैं!लेकिन अगर आप वास्तव में GraphvizLabel क्षमता मतलब है Show क्षमता चाहते हैं, यह आप क्या जरूरत है:

{-# LANGUAGE TypeSynonymInstances, FlexibleInstances 
,   UndecidableInstances, OverlappingInstances #-} 

-- Leave the module declaration, imports, formatting code, and class declaration 
-- the same. 

instance GraphvizLabel String where 
    toGVItem = quoted 

instance Show a => GraphvizLabel a where 
    toGVItem = quoted . show 

instance (GraphvizLabel k, GraphvizLabel v) => GraphvizLabel (Map k v) where 
    toGVItem = let kvfn k v = ((toGVItem k ++ "=" ++ toGVItem v) :) 
       in intercalate "," . M.foldWithKey kvfn [] 
    toGVLabel = squareBrackets . toGVItem 

सूचना है कि अपने विशिष्ट मामलों (GraphvizLabel String और GraphvizLabel (Map k v)) एक ही रहना; आपने Int और Char मामलों को GraphvizLabel a मामले में अभी तक ध्वस्त कर दिया है। याद रखें, UndecidableInstances का अर्थ यह है कि यह वास्तव में क्या कहता है: संकलक नहीं बता सकता है अगर उदाहरण चेक करने योग्य हैं या इसके बजाय टाइपशेकर लूप बना देंगे! इस मामले में, मुझे उचित रूप से यकीन है कि यहां सबकुछ वास्तव में निर्णायक है (लेकिन यदि कोई नोटिस करता है कि मैं गलत हूं, कृपया मुझे बताएं)। फिर भी, UndecidableInstances का उपयोग सावधानी से हमेशा संपर्क किया जाना चाहिए।

9

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

यहां सबसे साफ जवाब अलग-अलग प्रकार के लिए convert फ़ंक्शंस का उपयोग करने के लिए है, और convert पर निर्भर करता है कि यह किसी भी प्रकार का तर्क (उच्च आदेश फ़ंक्शन) पर निर्भर करता है।

तो Graphviz (यह भद्दा कोड को फिर से डिज़ाइन परहेज) में मैं graphviz समारोह को संशोधित तरह देखने के लिए होगा:

graphvizWithLabeler :: (a -> String) -> ... -> String 
graphvizWithLabeler labeler ... = 
    ... 
    where sa = labeler a 

और फिर graphviz है तुच्छता से इसे करने के लिए प्रतिनिधि: फिर graphviz जारी है

graphviz = graphvizWithLabeler sl 

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

ऐसे ग्राफों के लिए जिनके नोड्स Maps हैं, graphvizWithLabeler processMap2FancyKVString का उपयोग करें, अन्यथा graphviz का उपयोग करें। उच्च निर्णय कार्यों या टाइपक्लास विधियों के रूप में प्रासंगिक चीजों को ले कर जितना संभव हो सके इस निर्णय को स्थगित कर दिया जा सकता है।

आप Map रों और अन्य चीजों का उसी ग्राफ़ की coexisting की आवश्यकता है, तो आप एक एक प्रकार सब कुछ एक नोड हो सकता है का निवास खोजने की जरूरत है। यह टॉमएमडी के सुझाव के समान है। उदाहरण के लिए:

data NodeType 
    = MapNode (Map.Map Foo Bar) 
    | IntNode Int 

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

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

क्या यह आपको बताता था कि आप जो चाहते थे उसे पूरा करने के लिए आपको क्या जानने की आवश्यकता थी?

संबंधित मुद्दे