2009-06-15 8 views
16

मान लीजिए मैं निम्नलिखित है:Haskell बहुरूपता और सूचियों

class Shape a where 
    draw a :: a -> IO() 

data Rectangle = Rectangle Int Int 

instance Shape Rectangle where 
    draw (Rectangle length width) = ... 

data Circle = Circle Int Int 

instance Shape Circle where 
    draw (Circle center radius) = ... 

मेरे लिए किसी भी तरह से पार सूची पर, आकार की एक सूची परिभाषित करने के लिए, और कॉल प्रत्येक आकार पर ड्रॉ समारोह है? निम्नलिखित कोड संकलन नहीं करेगा क्योंकि सूची तत्वों सभी एक ही प्रकार नहीं कर रहे हैं:

shapes = [(Circle 5 10), (Circle 20, 30), (Rectangle 10 15)] 

मैं जानता हूँ कि मैं एक OO तरह से सोच रहा हूँ और हास्केल में इसे लागू करने की कोशिश कर, और कहा कि नहीं हो सकता है सर्वोत्तम दृष्टिकोण उन कार्यक्रमों के लिए सबसे अच्छा हास्केल दृष्टिकोण क्या होगा जिन्हें विभिन्न प्रकार की वस्तुओं के संग्रह से निपटने की आवश्यकता है?

उत्तर

21

तुम सच में ऐसा करने की जरूरत है, तो का उपयोग एक existential:

{-# LANGUAGE GADTs #-} 


class IsShape a where 
    draw :: a -> IO() 

data Rectangle = Rectangle Int Int 

instance IsShape Rectangle where 
    draw (Rectangle length width) = ... 

data Circle = Circle Int Int 

instance IsShape Circle where 
    draw (Circle center radius) = ... 

data Shape where 
    Shape :: IsShape a => a -> Shape 

shapes = [Shape (Circle 5 10), Shape (Circle 20 30), Shape (Rectangle 10 15)] 

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

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

+1

मुझे संकलन करने के लिए आपका उदाहरण नहीं मिल सका। लेकिन, आपके द्वारा संदर्भित विकी पेज पूरी तरह से मेरे प्रश्न का उत्तर देता है। –

+0

अभी आज़माएं - मेरे पास 'आकार' था जहां मुझे 'आकार' के लिए डेटा कन्स्ट्रक्टर के हस्ताक्षर में 'IsShape' होना चाहिए था। –

+2

भविष्य के पाठकों के लिए यह ध्यान देने योग्य है कि इस दृष्टिकोण का नुकसान यह है कि 'आकार' कंटेनर से मंडल और आयतों को प्राप्त करने वाला कोड 'IsShape' उदाहरण में दिए गए किसी भी * अन्य गुणों का उपयोग करने में सक्षम नहीं होगा; आप निर्देशांक पर नहीं पहुंच सकते हैं, यह नहीं बता सकते कि यह 'सर्किल' या 'आयत' है, और किसी भी अन्य कार्यों को कॉल नहीं कर सकता जो विशेष रूप से किसी भी आकार के बजाय मंडलियों या आयतों पर काम करता है। यह खुलेपन का मौलिक परिणाम है गणेश के बारे में बात कर रहा है (और जब आप "डाउनकास्ट" करने के लिए मजबूर महसूस करते हैं तो स्थिर रूप से टाइप किए गए ओओ प्रोग्रामिंग में दिखाया जाता है)। – Ben

13

अलग-अलग प्रकार और टाइपक्लास के बजाय एक प्रकार का उपयोग करने पर विचार करें।

data Shape = Rectangle Int Int 
      | Circle Int Int 

draw (Rectangle length width) = ... 
draw (Circle center radius) = ... 

shapes = [Circle 5 10, Circle 20 30, Rectangle 10 15] 
+6

हालांकि यह काम करता है, मैं एक समाधान नए डेटा प्रकार कहीं से परिभाषित करने की ताकि कोड है कि आकार सूची प्रक्रियाओं आकार के हर प्रकार के बारे में पता होना करने के लिए की जरूरत नहीं है की अनुमति देता है कि के लिए देख रहा था। –

5

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

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

हालांकि, इस मामले में, जब आप दूर प्रकार की जानकारी फेंकना चाहते हैं (आप केवल मूल्य के खींचने योग्य भाग में रूचि रखते हैं), तो आप अपने मूल्यों को उस प्रकार के रूप में बदलने का सुझाव देंगे जो केवल खींचने योग्य है।

type Drawable = IO() 

shapes :: [Drawable] 
shapes = [draw (Circle 5 10), draw (Circle 20 30), draw (Rectangle 10 15)] 

मुमकिन है, अपने वास्तविक कुछ Drawable हो जाएगा बस IO() से अधिक दिलचस्प (शायद कुछ की तरह: MaxWidth -> IO())।

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


बस पूरा हो (और यह जवाब में मेरी टिप्पणी को शामिल) के लिए: यह एक अधिक सामान्य कार्यान्वयन है उपयोगी अगर Shape अधिक कार्य करता है:

type MaxWith = Int 

class Shape a where 
    draw :: a -> MaxWidth -> IO() 
    size :: a -> Int 

type ShapeResult = (MaxWidth -> IO(), Int) 

shape :: (Shape a) => a -> ShapeResult 
shape x = (draw x, size x) 

shapes :: [ShapeResult] 
shapes = [shape (Circle 5 10), shape (Circle 20 30), shape (Rectangle 10 15)] 

यहाँ, shape समारोह बदल देती है एक Shape aShape कक्षा में सभी कार्यों को बस कॉल करके ShapeResult मान में मूल्य।आलस्य के कारण, जब तक आपको उनकी आवश्यकता न हो, तब तक किसी भी मूल्य का वास्तव में गणना नहीं की जाती है।

ईमानदार होने के लिए, मुझे नहीं लगता कि मैं वास्तव में इस तरह के निर्माण का उपयोग करूंगा। मैं या तो ऊपर से Drawable-विधि का उपयोग करता हूं, या यदि अधिक सामान्य समाधान की आवश्यकता है, तो GADT का उपयोग करें। कहा जा रहा है, यह एक मजेदार व्यायाम है।

+0

यह अच्छा है अगर चित्र वास्तव में उन सभी के साथ करना चाहते हैं - यदि आप 'आकार' वर्ग से अलग-अलग चीजें करना चाहते हैं, तो यह सब ठीक से स्केल नहीं करता है - हालांकि कुछ इंद्रियों में मेरा समाधान प्राकृतिक है इस दृष्टिकोण का विस्तार क्योंकि यह पूरे वर्ग शब्दकोश के चारों ओर प्रत्येक मान के साथ गुजरता है जिसे आप चाहें तो लागू कर सकते हैं। –

+0

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

+0

गणेश की टिप्पणी भी मेरी प्रतिक्रिया थी। यह हास्केल के आलसी मूल्यांकन का वास्तव में दिलचस्प उपयोग दिखाता है। वह अच्छा है। हास्केल के लिए नया होने के नाते, मुझे यह मानना ​​है कि मुझे आलसी मूल्यांकन का लाभ उठाने के लिए उपयोग करने के लिए थोड़ा सा समय लगेगा। –

5

एक तरह से यह करने के लिए vtables साथ होगा:

data Shape = Shape { 
    draw :: IO(), 
    etc :: ... 
} 

rectangle :: Int -> Int -> Shape 
rectangle len width = Shape { 
    draw = ..., 
    etc = ... 
} 

circle :: Int -> Int -> Shape 
circle center radius = Shape { 
    draw = ..., 
    etc = ... 
} 
+1

यहां समस्या यह है कि आपके आकार डेटा प्रकार को सभी संभावित आकारों के सभी क्षेत्रों का एक संघ होना चाहिए। यह मेरे (प्रदूषित) मामले के लिए ठीक काम कर सकता है, लेकिन यह अधिक जटिल मामलों में एक अजीब दृष्टिकोण होगा। –

+1

दिलचस्प जवाब, इस तरह कुछ नहीं सोचा था। @Clint: मुझे नहीं लगता कि यहां 'आकार' डेटा प्रकार सभी संभावित क्षेत्रों का संघ होना चाहिए। यह आपके 'आकार' वर्ग के सभी कार्यों का वर्णन करता है, प्रत्येक कन्स्ट्रक्टर फ़ंक्शन (आयताकार, सर्कल) कार्यों के लिए एक अद्वितीय कार्यान्वयन प्रदान करके एक आकार बनाता है, उदाहरण के साथ। –

+1

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

1

एक विषम आकार के हास्केल में सूची के साथ कैसे निपटने के लिए - सार बहुरूपता प्रकार वर्गों के साथ: के माध्यम से http://pastebin.com/hL9ME7qP @pastebin

कोड:

{-# LANGUAGE GADTs #-} 

class Shape s where 
area :: s -> Double 
perimeter :: s -> Double 

data Rectangle = Rectangle { 
width :: Double, 
height :: Double 
} deriving Show 

instance Shape Rectangle where 
area rectangle = (width rectangle) * (height rectangle) 
perimeter rectangle = 2 * ((width rectangle) + (height rectangle)) 

data Circle = Circle { 
radius :: Double 
} deriving Show 

instance Shape Circle where 
area circle = pi * (radius circle) * (radius circle) 
perimeter circle = 2.0 * pi * (radius circle) 

r=Rectangle 10.0 3.0 
c=Circle 10.0 
list=[WrapShape r,WrapShape c] 

data ShapeWrapper where 
WrapShape :: Shape s => s -> ShapeWrapper 

getArea :: ShapeWrapper -> Double 
getArea (WrapShape s) = area s 

getPerimeter :: ShapeWrapper -> Double 
getPerimeter (WrapShape s) = perimeter s 

areas = map getArea list 
perimeters = map getPerimeter list 
0

इसके बजाय विद्यमान मात्रात्मक वाक्यविन्यास का उपयोग करके गणेश के समाधान का एक संस्करण।

{-# LANGUAGE ExistentialQuantification #-} 
class IsShape a where 
    draw :: a -> String 

data Rectangle = Rectangle Int Int 

instance IsShape Rectangle where 
    draw (Rectangle length width) = "" 

data Circle = Circle Int Int 

instance IsShape Circle where 
    draw (Circle center radius) = "" 

data Shape = forall a. (IsShape a) => Shape a 

shapes = [Shape (Circle 5 10), Shape (Circle 20 30), Shape (Rectangle 10 15)]