2013-09-28 9 views
7

नीचे काम करने के लिए लगता है ... लेकिन यह अनाड़ी लगता है।अस्तित्व antipattern, कैसे से बचने के लिए

data Point = Point Int Int 
data Box = Box Int Int 
data Path = Path [Point] 
data Text = Text 

data Color = Color Int Int Int 
    data WinPaintContext = WinPaintContext Graphics.Win32.HDC 

class CanvasClass vc paint where 
    drawLine :: vc -> paint -> Point -> Point -> IO() 
    drawRect :: vc -> paint -> Box -> IO() 
    drawPath :: vc -> paint -> Path -> IO() 

class (CanvasClass vc paint) => TextBasicClass vc paint where 
    basicDrawText :: vc -> paint -> Point -> String -> IO() 

instance CanvasClass WinPaintContext WinPaint where 
    drawLine = undefined 
    drawRect = undefined 
    drawPath = undefined 

instance TextBasicClass WinPaintContext WinPaint where 
    basicDrawText (WinPaintContext a) = winBasicDrawText a 

op :: CanvasClass vc paint => vc -> Box -> IO() 
op canvas _ = do 
    basicDrawText canvas WinPaint (Point 30 30) "Hi" 

open :: IO() 
open = do 
    makeWindow (Box 300 300) op 

winBasicDrawText :: Graphics.Win32.HDC -> WinPaint -> Point -> String -> IO() 
winBasicDrawText hdc _ (Point x y) str = do 
    Graphics.Win32.setBkMode hdc Graphics.Win32.tRANSPARENT 
    Graphics.Win32.setTextColor hdc (Graphics.Win32.rgb 255 255 0) 
    Graphics.Win32.textOut hdc 20 20 str 
    return() 

windowsOnPaint :: (WinPaintContext -> Box -> IO()) -> 
        Graphics.Win32.RECT -> 
        Graphics.Win32.HDC -> 
        IO() 
windowsOnPaint f rect hdc = f (WinPaintContext hdc) (Box 30 30) 

makeWindow :: Box -> (WinPaintContext -> Box -> IO()) -> IO() 
makeWindow (Box w h) onPaint = 
    Graphics.Win32.allocaPAINTSTRUCT $ \ lpps -> do 
    hwnd <- createWindow w h (wndProc lpps (windowsOnPaint onPaint)) 
    messagePump hwnd 

अब, क्या पसंदीदा तरीका प्रतीत हो रहा है बस

data Canvas = Canvas { 
    drawLine :: Point -> Point -> IO(), 
    drawRect :: Box -> IO(), 
    drawPath :: Path -> IO() 
} 

hdc2Canvas :: Graphics.Win32.HDC -> Paint -> IO (Canvas) 
hdc2Canvas hdc paint = Canvas { drawLine = winDrawLine hdc paint ... } 

हालांकि है ...

हम पेंट के आसपास रखने के लिए और ड्राइंग प्रक्रिया के दौरान उन्हें उत्परिवर्तित चाहते, क्योंकि वे बनाने और नष्ट करने के लिए महंगे हैं। एक रंग की तरह [bgcolor लाल, fgColor नीले, फ़ॉन्ट "Tahoma"] या कुछ और एक सूची हो सकता है, या यह एक आंतरिक संरचना करने के लिए एक सूचक हो सकता है रंग प्रणाली का उपयोग करता है (यह विंडोज़ GDI पर एक अमूर्त है, लेकिन अंततः सार होगा Direct2D और CoreGraphics) है, जो है "रंग" वस्तुओं जो मैं अधिक से अधिक पुन: नहीं है चाहता हूँ और फिर से अधिक बाँध के ऊपर।

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

data Paint = Paint { 
    setFg :: Color -> IO() , 
    setBg :: Color -> IO() 
} 

मैं पॉइंटर कहां रख सकता हूं? जब मैं कैनवास में कुछ फ़ंक्शन को पेंट देता हूं, तो वह सूचक कैसे प्राप्त करता है? इस एपीआई को डिजाइन करने का सही तरीका क्या है?

+0

'WinPaint' क्या है? क्या आपके पास एक ऐसा मुख्य भाग है जो कुछ चलाता है और करता है, इसलिए हम देख सकते हैं कि यह सब क्या करना है? अस्तित्व में क्वांटिफाइड प्रकार कहां है जिसे आप खत्म करना चाहते हैं? – Cirdec

+0

विनपेंट केवल कुछ ऐसा है जो किसी प्रकार के प्लेटफॉर्म विशिष्ट ड्राइंग संदर्भ में एक पॉइंटर लपेटता है जिसमें अग्रभूमि, पृष्ठभूमि, फ़ॉन्ट इत्यादि शामिल हैं। मैंने स्पष्ट रूप से उन्हें प्रमाणित रूप से प्रमाणित नहीं किया है, लेकिन मैं अस्तित्व में कैनवास क्लास को कैनवास को प्रमाणित करता हूं। यहां "ओपन" मुख्य है, और इसमें केवल कुछ पाठ के साथ एक विंडो खोलनी है। यह कोड काम नहीं करता है, लेकिन मुझे उम्मीद है कि यह मेरा इरादा पूरा हो जाएगा। – Evan

+0

"इस एपीआई को डिजाइन करने का सही तरीका क्या है?" programmers.stackexchange.com के लिए अधिक उपयुक्त लगता है। आपको उस प्रश्न के लिए काफी विविध प्रतिक्रियाएं मिलेंगी। मेरा, उदाहरण के लिए, "आईओ() से छुटकारा पाएं" या "चमक कैसे करता है" के साथ शुरू होगा, इनमें से कोई भी अस्तित्वत्मक मात्रा के साथ कुछ भी नहीं करना है। – Cirdec

उत्तर

9

इंटरफ़ेस

सबसे पहले, आप से पूछना होगा "मेरी आवश्यकताएं क्या हैं?"। सादे अंग्रेजी में चलो राज्य हम एक कैनवास क्या करना चाहते हैं क्या (ये मेरे अनुमान आपके प्रश्न पर आधारित हैं):

  • कुछ कैनवस उन पर डाल आकार
  • कुछ कैनवस पाठ उन पर
  • डाल दिया है सकते हैं हो सकता है
  • कुछ कैनवस बदल क्या वे एक पेंट
  • हम पेंट अभी तक क्या कर रहे हैं पता नहीं है के आधार पर करते हैं, लेकिन वे अब हम हास्केल में इन विचारों का अनुवाद विभिन्न कैनवस

के लिए अलग होगा। हास्केल एक "प्रकार-प्रथम" भाषा है, इसलिए जब हम आवश्यकताओं और डिजाइन के बारे में बात कर रहे हैं, तो हम शायद प्रकारों के बारे में बात कर रहे हैं।

  • हास्केल में, जब हम प्रकारों के बारे में बात करते समय "कुछ" शब्द देखते हैं, तो हम प्रकार के वर्गों के बारे में सोचते हैं। उदाहरण के लिए, show वर्ग कहते हैं, "कुछ प्रकार के तार के रूप में प्रतिनिधित्व किया जा सकता है"।
  • जब हम किसी चीज़ के बारे में बात करते हैं जिसे हम अभी तक नहीं जानते हैं, आवश्यकताओं के बारे में बात करते समय, यह एक प्रकार है जहां हम नहीं जानते कि यह अभी तक क्या है। यह एक प्रकार चर है।
  • "उन्हें डालें" ऐसा लगता है कि हम एक कैनवास लेते हैं, उस पर कुछ डालते हैं, और फिर एक कैनवास लेते हैं।

अब हम इन आवश्यकताओं में से प्रत्येक के लिए कक्षाओं लिख सकते हैं:

class ShapeCanvas c where -- c is the type of the Canvas 
    draw :: Shape -> c -> c 

class TextCanvas c where 
    write :: Text -> c -> c 

class PaintCanvas p c where -- p is the type of Paint 
    load :: p -> c -> c 

प्रकार चर c केवल एक बार प्रयोग किया जाता है, c -> c के रूप में प्रदर्शित।इससे पता चलता है कि हम को c के साथ बदलकर इन अधिक सामान्य बना सकते हैं।

class ShapeCanvas c where -- c is the type of the canvas 
    draw :: Shape -> c 

class TextCanvas c where 
    write :: Text -> c 

class PaintCanvas p c where -- p is the type of paint 
    load :: p -> c 

अब PaintCanvas एक class कि हास्केल में समस्याग्रस्त है जैसा दिखता है। यह के लिए प्रकार प्रणाली यह पता लगाने की क्या की तरह

class Implicitly a b where 
    convert :: b -> a 

कक्षाओं में हो रहा है मैं PaintCanvas बदलते TypeFamilies विस्तार का लाभ लेने के द्वारा इस को कम करता हूँ मुश्किल है।

class PaintCanvas c where 
    type Paint c :: * -- (Paint c) is the type of Paint for canvases of type c 
    load :: (Paint c) -> c 

अब, चलो हमारे इंटरफेस के लिए सब कुछ एक साथ रखा, आकार और पाठ के लिए अपने डेटा प्रकार सहित जाने (मेरे लिए समझ बनाने के लिए संशोधित):

{-# LANGUAGE TypeFamilies #-} 

module Data.Canvas (
    Point(..), 
    Shape(..), 
    Text(..), 
    ShapeCanvas(..), 
    TextCanvas(..), 
    PaintCanvas(..) 
) where 

data Point = Point Int Int 

data Shape = Dot Point 
      | Box Point Point 
      | Path [Point] 

data Text = Text Point String 

class ShapeCanvas c where -- c is the type of the Canvas 
    draw :: Shape -> c 

class TextCanvas c where 
    write :: Text -> c 

class PaintCanvas c where 
    type Paint c :: * -- (Paint c) is the type of Paint for canvases of type c 
    load :: (Paint c) -> c 

कुछ उदाहरण

यह अनुभाग उपयोगी कैनवास के लिए अतिरिक्त आवश्यकताएं पेश करेगा, इसके अलावा हमने पहले से ही काम किया है। कैनवास कक्षाओं में c के साथ c -> c को प्रतिस्थापित करते समय हमने जो खो दिया, उसका यह एनालॉग है।

चलो अपने पहले उदाहरण कोड, op से शुरू करते हैं। हमारे नए इंटरफ़ेस के साथ यह बस है:

op :: (TextCanvas c) => c 
op = write $ Text (Point 30 30) "Hi" 

चलिए थोड़ा और जटिल उदाहरण बनाते हैं। एक "एक्स" खींचने वाली चीज़ के बारे में कैसे? हम "एक्स"

ex :: (ShapeCanvas c) => c 
ex = draw $ Path [Point 10 10, Point 20 20] 

के पहले स्ट्रोक कर सकते हैं लेकिन हम कोई रास्ता नहीं पार स्ट्रोक के लिए एक और Path जोड़ने के लिए। हमें दो ड्राइंग चरणों को एक साथ रखने के लिए कुछ रास्ता चाहिए। टाइप c -> c -> c के साथ कुछ सही होगा। सबसे आसान हास्केल क्लास मैं सोच सकता हूं कि यह Monoid a के mappend :: a -> a -> a प्रदान करता है। Monoid को एक पहचान और सहयोगीता की आवश्यकता है। क्या यह मानने योग्य है कि कैनवस पर एक ड्राइंग ऑपरेशन है जो उन्हें छूता है? यह काफी उचित लगता है। क्या यह मानना ​​उचित है कि एक ही क्रम में किए गए तीन ड्राइंग ऑपरेशन, वही काम करते हैं, भले ही पहले दो एक साथ किए जाते हैं, और फिर तीसरा, या यदि पहला प्रदर्शन किया जाता है, और फिर दूसरा और तीसरा एक साथ किया जाता है ? फिर, यह मेरे लिए काफी उचित लगता है। यह सुझाव हम ex के रूप में लिख सकते हैं:

randomDrawing :: (MonadIO m, ShapeCanvas (m()), TextCanvas (m())) => m() 
randomDrawing = do 
    index <- liftIO . getStdRandom $ randomR (0,2) 
    choices !! index   
    where choices = [op, ex, return()] 

यह काफी काम है, क्योंकि हम डॉन नहीं है ':

ex :: (Monoid c, ShapeCanvas c) => c 
ex = (draw $ Path [Point 10 10, Point 20 20]) `mappend` (draw $ Path [Point 10 20, Point 20 10]) 

अंत में, चलो कुछ इंटरैक्टिव, कि कुछ बाहरी के आधार पर आकर्षित करने के लिए क्या निर्णय लेता है पर विचार करते हैं टी (Monad m) => Monoid (m()) के लिए एक उदाहरण है ताकि ex काम करेगा। हम reducers पैकेज से Data.Semigroup.Monad का उपयोग कर सकते हैं, या खुद को जोड़ सकते हैं, लेकिन यह हमें अंतर्निहित उदाहरण भूमि में डाल देता है। यह करने के लिए पूर्व बदलने के लिए आसान हो जाएगा:

ex :: (Monad m, ShapeCanvas (m())) => m() 
ex = do 
    draw $ Path [Point 10 10, Point 20 20] 
    draw $ Path [Point 10 20, Point 20 10] 

लेकिन प्रकार प्रणाली काफी को समझ नहीं सकता है कि पहली draw से इकाई दूसरे से इकाई के रूप में एक ही है।हमारी कठिनाई यहां अतिरिक्त आवश्यकताओं को पता चलता है, कि हम नहीं काफी में पहली पर हमारे उंगली डाल सकता है:

  • कैनवस मौजूदा आपरेशन के दृश्यों का विस्तार, आदि ड्राइंग के लिए आपरेशन प्रदान करने पाठ लिखने,

चोरी सीधे http://www.haskellforall.com/2013/06/from-zero-to-cooperative-threads-in-33.html:

  • जब आप "निर्देशों का अनुक्रम" सुनते हैं तो आपको यह सोचना चाहिए: "monad"।
  • जब आप "विस्तार" के साथ अर्हता प्राप्त करते हैं तो आपको यह सोचना चाहिए: "मोनड ट्रांसफार्मर"।

अब हम महसूस करते हैं कि हमारे कैनवास कार्यान्वयन संभवतः एक मोनाड ट्रांसफॉर्मर होने जा रहा है। हम अपने इंटरफेस पर वापस जा सकते हैं, और इसे बदल सकते हैं ताकि प्रत्येक वर्ग एक मोनैड के लिए एक वर्ग हो, ट्रांसफार्मर के MonadIO वर्ग और एमटीएल के मोनैड वर्गों के समान।

इंटरफेस,

{-# LANGUAGE TypeFamilies #-} 

module Data.Canvas (
    Point(..), 
    Shape(..), 
    Text(..), 
    ShapeCanvas(..), 
    TextCanvas(..), 
    PaintCanvas(..) 
) where 

data Point = Point Int Int 

data Shape = Dot Point 
      | Box Point Point 
      | Path [Point] 

data Text = Text Point String 

class Monad m => ShapeCanvas m where -- c is the type of the Canvas 
    draw :: Shape -> m() 

class Monad m => TextCanvas m where 
    write :: Text -> m() 

class Monad m => PaintCanvas m where 
    type Paint m :: * -- (Paint c) is the type of Paint for canvases of type c 
    load :: (Paint m) -> m() 

उदाहरण पर दोबारा गौर किया,

अब हमारे उदाहरण के सभी ड्राइंग संचालन किसी अज्ञात Monad मीटर में कार्रवाई कर रहे हैं पर दोबारा गौर किया:

op :: (TextCanvas m) => m() 
op = write $ Text (Point 30 30) "Hi" 

ex :: (ShapeCanvas m) => m() 
ex = do 
    draw $ Path [Point 10 10, Point 20 20] 
    draw $ Path [Point 10 20, Point 20 10] 


randomDrawing :: (MonadIO m, ShapeCanvas m, TextCanvas m) => m() 
randomDrawing = do 
    index <- liftIO . getStdRandom $ randomR (0,2) 
    choices !! index   
    where choices = [op, ex, return()] 

हम कर सकते हैं पेंट का उपयोग करके एक उदाहरण भी बनाते हैं। जब से हम पेंट क्या उपलब्ध नहीं होगा पता नहीं है, वे सब बाहर से (उदाहरण के लिए तर्क के रूप में) उपलब्ध कराया जाना है:

checkerBoard :: (ShapeCanvas m, PaintCanvas m) => Paint m -> Paint m -> m() 
checkerBoard red black = 
    do 
     load red 
     draw $ Box (Point 10 10) (Point 20 20) 
     draw $ Box (Point 20 20) (Point 30 30) 
     load black 
     draw $ Box (Point 10 20) (Point 20 30) 
     draw $ Box (Point 20 10) (Point 30 20) 

एक कार्यान्वयन

आप करने के लिए अपने कोड काम कर सकते हैं अमूर्तता के बिना विभिन्न पेंट्स का उपयोग करके अंक, बक्से, रेखाएं और पाठ खींचें, हम इसे पहले खंड से इंटरफेस को लागू करने के लिए बदल सकते हैं।

+0

मैंने टाइप कक्षाओं से बचने से पहले टिप्पणियां पढ़ी हैं, जब तक कि वे आवश्यक न हों, और उस प्रकार के वर्ग आमतौर पर "केवल" उपयोगी होते हैं जब आप जिस संरचना से निपटते हैं, उसे भी कानूनों के अनुसार व्यवहार करना चाहिए। इस पर आपका क्या लेना है, क्योंकि आप टाइप क्लास का उपयोग करते हैं (जो मैं बता सकता हूं) उदारता से? मैं सिर्फ एक नौसिखिया हूं और अगर मैं इस (संभावित रूप से बहुत बेवकूफ) प्रश्न के साथ लाइन से बाहर हूं तो बस मुझे बताओ।मुझे एक ओओपी दुनिया से आने वाले हास्केल के साथ कोड बनाने के तरीके को समझने में कठिनाई हो रही है! – kqr

+1

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

+0

यह बिल्कुल अविश्वसनीय है। आपके समय और विस्तार और अद्भुत पेसिंग के लिए बहुत बहुत धन्यवाद! – Evan

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