2012-12-17 24 views
14

में एकाधिक राज्यों का संयोजन मैं एक प्रोग्राम लिख रहा हूं जो डेमॉन के रूप में चलता है। डेमॉन बनाने के लिए, उपयोगकर्ता के लिए आवश्यक वर्गों (उनमें से एक एक डेटाबेस है) इन कक्षाओं में है कार्यों प्रपत्र StateT s IO a, की प्रकार हस्ताक्षर है, वे लेकिन s के लिए अलग है में से प्रत्येक के लिए कार्यान्वयन का एक सेट की आपूर्ति प्रत्येक वर्ग।स्टेटटी

import Control.Monad (liftM) 
import Control.Monad.State (StateT(..), get) 

class Hammer h where 
    driveNail :: StateT h IO() 

data ClawHammer = MkClawHammer Int -- the real implementation is more complex 

instance Hammer ClawHammer where 
    driveNail = return() -- the real implementation is more complex 

-- Plus additional classes for wrenches, screwdrivers, etc. 

अब मैं एक रिकॉर्ड कार्यान्वयन प्रत्येक "स्लॉट" के लिए उपयोगकर्ता द्वारा चुना प्रतिनिधित्व करता है कि परिभाषित कर सकते हैं:

वर्गों में से प्रत्येक के इस पद्धति का अनुसरण करता मान लीजिए।

data MultiTool h = MultiTool { 
    hammer :: h 
    -- Plus additional fields for wrenches, screwdrivers, etc. 
    } 

और डेमॉन StateT (MultiTool h ...) IO() इकाई में अपना काम का सबसे करता है।

अब, क्योंकि multitool में एक हथौड़ा है, मैं इसे किसी भी स्थिति में उपयोग कर सकता हूं जहां एक हथौड़ा की आवश्यकता होती है। दूसरे शब्दों में, MultiTool प्रकार कक्षाएं इसमें से किसी को लागू कर सकते हैं, अगर मैं इस तरह कोड लिखने:

stateMap :: Monad m => (s -> t) -> (t -> s) -> StateT s m a -> StateT t m a 
stateMap f g (StateT h) = StateT $ liftM (fmap f) . h . g 

withHammer :: StateT h IO() -> StateT (MultiTool h) IO() 
withHammer runProgram = do 
    t <- get 
    stateMap (\h -> t {hammer=h}) hammer runProgram 

instance Hammer h => Hammer (MultiTool h) where 
    driveNail = withHammer driveNail 

लेकिन के कार्यान्वयन withHammer, withWrench, withScrewdriver, आदि मूल रूप से समान हैं। यह कुछ इस तरह लिखने के लिए सक्षम होने के लिए अच्छा होगा ...

--withMember accessor runProgram = do 
-- u <- get 
-- stateMap (\h -> u {accessor=h}) accessor runProgram 

-- instance Hammer h => Hammer (MultiTool h) where 
-- driveNail = withMember hammer driveNail 

लेकिन निश्चित है कि संकलन नहीं होगा की।

मुझे संदेह है कि मेरा समाधान बहुत ऑब्जेक्ट उन्मुख है। क्या कोई बेहतर तरीका है? मोनाड ट्रांसफार्मर, शायद? किसी भी सुझाव के लिए अग्रिम धन्यवाद।

+0

संयोग से, मैं एक त्वरित संपादित अपने कोड है क्योंकि आपके सरलीकरण में 'ClawHammer' के कार्यान्वयन को छोड़ते हुए बनाया आपने ऐसा कुछ बनाया जो शायद आपके पास नहीं था। –

उत्तर

24

यदि आप अपने मामले में एक बड़े वैश्विक राज्य के साथ जाना चाहते हैं, तो बेन का सुझाव दिया गया है कि आप क्या उपयोग करना चाहते हैं। मैं भी एडवर्ड Kmett के लेंस पुस्तकालय की सलाह देते हैं। हालांकि, एक और, शायद अच्छा तरीका है।

सर्वरों की संपत्ति है जो प्रोग्राम लगातार चलता है और एक राज्य अंतरिक्ष पर एक ही ऑपरेशन करता है।समस्या तब शुरू होती है जब आप अपने सर्वर को मॉड्यूलर करना चाहते हैं, इस स्थिति में आप केवल कुछ वैश्विक स्थिति से अधिक चाहते हैं। आप मॉड्यूल चाहते हैं कि उनका अपना राज्य हो।

के कुछ के रूप में एक मॉड्यूल है कि एक रिस्पांस करने के लिए एक अनुरोध बदल देती है के बारे में सोच है:

Module :: (Request -> m Response) -> Module m 

अब अगर यह कुछ राज्य है, तो यह कहा गया है कि में उल्लेखनीय हो जाता है मॉड्यूल एक अलग दे सकता है अगली बार जवाब दें।

Module :: s -> ((Request, s) -> m (Response s)) -> Module m 

लेकिन एक बहुत अच्छे और बराबर तरीका यह व्यक्त करने के लिए किया जाता है निम्नलिखित निर्माता (हम एक प्रकार उसके चारों ओर जल्द ही निर्माण करेगा):

तरीके यह करने के लिए के एक नंबर, उदाहरण के लिए निम्नलिखित हैं
Module :: (Request -> m (Response, Module m)) -> Module m 

यह मॉड्यूल एक प्रतिक्रिया के लिए अनुरोध करता है, लेकिन साथ ही साथ स्वयं का एक नया संस्करण भी देता है। के आगे जाकर अनुरोध और प्रतिक्रियाओं बहुरूपी करते हैं:

Module :: (a -> m (b, Module m a b)) -> Module m a b 

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

newtype Module m a b = 
    Module (a -> m (b, Module m a b)) 

instance (Monad m) => Applicative (Module m a) 
instance (Monad m) => Arrow (Module m) 
instance (Monad m) => Category (Module m) 
instance (Monad m) => Functor (Module m a) 

अब हम दो मॉड्यूल लिख सकते हैं जिनके बारे में जानने के बिना अपने व्यक्तिगत स्थानीय राज्य भी हैं! लेकिन यह पर्याप्त नहीं है। हमें और चाहिए। मॉड्यूल के बारे में कैसे स्विच किया जा सकता है?

newtype Module m a b = 
    Module (a -> m (Maybe b, Module m a b)) 

इस रचना का दूसरा रूप है कि (.) के लिए ओर्थोगोनल है की अनुमति देता है:: चलो एक जवाब देने के लिए हमारे छोटे से मॉड्यूल प्रणाली ऐसी है कि मॉड्यूल वास्तव में नहीं चुन सकते हैं का विस्तार करते हैं अब हमारी प्रकार भी Alternative functors के एक परिवार है:

instance (Monad m) => Alternative (Module m a) 

अब एक मॉड्यूल चुन सकता है कि अनुरोध का जवाब देना है, और यदि नहीं, तो अगले मॉड्यूल की कोशिश की जाएगी। सरल। आपने अभी तार श्रेणी को फिर से शुरू किया है। =)

बेशक आपको इसे फिर से शुरू करने की आवश्यकता नहीं है। Netwire लाइब्रेरी इस डिज़ाइन पैटर्न को लागू करती है और पूर्वनिर्धारित "मॉड्यूल" (तारों कहा जाता है) की एक बड़ी लाइब्रेरी के साथ आता है। ट्यूटोरियल के लिए Control.Wire मॉड्यूल देखें।

+5

अनजाने में उत्कृष्ट उत्तर! – AndrewC

6

यह लेंस के आवेदन की तरह बहुत लगता है।

लेंस कुछ डेटा के उप-क्षेत्र का एक विनिर्देश हैं। विचार यह है कि आपके पास कुछ मूल्य toolLens है और view और set फ़ंक्शन हैं ताकि view toolLens :: MultiTool h -> h टूल प्राप्त कर सकें और set toolLens :: MultiTool h -> h -> MultiTool h इसे नए मान के साथ बदल देता है। फिर आप आसानी से एक लेंस स्वीकार करने के रूप में अपने withMember को परिभाषित कर सकते हैं।

लेंस प्रौद्योगिकी ने हाल ही में एक बड़ा सौदा किया है, और अब वे अविश्वसनीय रूप से सक्षम हैं। लेखन के समय के आसपास सबसे शक्तिशाली पुस्तकालय एडवर्ड Kmett की lens लाइब्रेरी है, जो निगलने के लिए थोड़ा सा है, लेकिन एक बार जब आप अपनी इच्छित सुविधाओं को ढूंढ लेते हैं तो बहुत आसान है। आप SO पर लेंस के बारे में और अधिक प्रश्न भी खोज सकते हैं, उदा। Functional lenses जो lenses, fclabels, data-accessor - which library for structure access and mutation is better से लिंक करता है, या lenses टैग।

14

यहां lens का उपयोग करने का एक ठोस उदाहरण है जैसे हर कोई बात कर रहा है। निम्नलिखित कोड उदाहरण में, Type1 स्थानीय स्थिति (यानी आपका हथौड़ा) है, और Type2 वैश्विक स्थिति (यानी आपका मल्टीटूल) है।

import Control.Lens 
import Control.Monad.Trans.Class (lift) 
import Control.Monad.Trans.State 

data Type1 = Type1 { 
    _field1 :: Int , 
    _field2 :: Double} 

field1 :: SimpleLens Type1 Int 
field1 = lens _field1 (\x a -> x { _field1 = a}) 

field2 :: SimpleLens Type1 Double 
field2 = lens _field2 (\x a -> x { _field2 = a}) 

data Type2 = Type2 { 
    _type1 :: Type1 , 
    _field3 :: String} 

type1 :: SimpleLens Type2 Type1 
type1 = lens _type1 (\x a -> x { _type1 = a}) 

field3 :: SimpleLens Type2 String 
field3 = lens _field3 (\x a -> x { _field3 = a}) 

localCode :: StateT Type1 IO() 
localCode = do 
    field1 += 3 
    field2 .= 5.0 
    lift $ putStrLn "Done!" 

globalCode :: StateT Type2 IO() 
globalCode = do 
    f1 <- zoom type1 $ do 
     localCode 
     use field1 
    field3 %= (++ show f1) 
    f3 <- use field3 
    lift $ putStrLn f3 

main = runStateT globalCode (Type2 (Type1 9 4.0) "Hello: ") 

zoom एक प्रकार के तत्काल उप क्षेत्रों तक सीमित नहीं है: lens आप एक स्थानीय राज्य गणना है कि किसी भी क्षेत्र एक लेंस द्वारा परिभाषित पर ज़ूम चलाने देता है जो zoom समारोह प्रदान करता है।के बाद से लेंस composable हैं, तो आप की तरह कुछ कर रही द्वारा के रूप में गहरी आप एक ही आपरेशन में चाहते हैं के रूप में ज़ूम कर सकते हैं:

zoom (field1a . field2c . field3b . field4j) $ do ... 
+0

इस दृष्टिकोण का अंतिम नकारात्मक पक्ष यह है कि 'टाइप 1' सीधे' टाइप 2 'के अंदर घोंसला है और उस प्रकार के पूर्ण ज्ञान की आवश्यकता है। यह अमूर्त रिसाव IMHO बनाता है। –