2014-09-24 8 views
12

Propellor में परस्पर विरोधी पोर्ट संख्या को रोकने के लिए एक प्रणाली यह [Property] के रूप में तैनात प्रतिनिधित्व करता है, और सरलीकरण के लिए, मान लें कि data Property = Property (Set Port) SatisfyPropertyप्रकार का उपयोग कर एक सूची

तो, वहाँ एक apacheInstalled संपत्ति है, जो पोर्ट 80 का उपयोग करता है हो सकता है चलो और 443, और एक torBridge संपत्ति, जो पोर्ट 443 का उपयोग करती है। यह एक ही समय में दोनों गुणों के लिए एक सिस्टम के लिए समझ में नहीं आता है, क्योंकि वे एक ही पोर्ट 443 का उपयोग करते हैं।

मुझे आश्चर्य है कि क्या कोई व्यवहार्य तरीका है सिस्टम को दोनों को असाइन करने से रोकने के लिए चेकर प्रकार के लिए? फिर निर्माण के समय पोर्ट बंदरगाहों को पकड़ा जा सकता है। मुझे लगता है कि टाइप लेवल इन्ट्स पहला कदम होगा, लेकिन मेरे पास दूसरे के लिए कोई सुराग नहीं है ..

+3

यह दर्दनाक होने वाला है। मैं अनुशंसा करता हूं [स्मार्ट कन्स्ट्रक्टर] (http://www.haskell.org/haskellwiki/Smart_constructors) – luqui

उत्तर

11

यह बहुत मुश्किल है, लेकिन एक अद्यतित ghc संस्करण (नवीनतम हैकेल) के साथ पूरी तरह से संभव है मंच काम करेगा)। इसके बारे में कई उदाहरण नहीं हैं (क्योंकि यह सब कुछ नया है), इसलिए मुझे उम्मीद है कि यह आपकी मदद करेगा।

आप सही हैं कि प्रकार के स्तर के प्राकृतिक उपयोग कर काम करेंगे। आपको दो मॉड्यूल की आवश्यकता होगी - एक संरचना को परिभाषित करने और एक सुरक्षित इंटरफ़ेस प्रदान करने के लिए, और वास्तविक सेवाओं को परिभाषित करने के लिए दूसरा।

{-# LANGUAGE TypeOperators, PolyKinds, RankNTypes #-} 
{-# LANGUAGE KindSignatures, DataKinds, TypeFamilies, UndecidableInstances #-} 
module DefinedServices where 
import ServiceTypes 
import Control.Monad 

apacheInstalled :: Service '[443] ServiceDetails 
apacheInstalled = makeService "apache" $ putStrLn "Apache service" 

torBridge :: Service [80,443] ServiceDetails 
torBridge = makeService "tor" $ putStrLn "Tor service" 

httpService :: Service [80, 8080] ServiceDetails 
httpService = makeService "http" $ putStrLn "Http service" 

serviceList1 :: [ServiceDetails] 
serviceList1 = getServices $ 
       noServices `addService` httpService `addService` apacheInstalled 

-- serviceList2 :: [ServiceDetails] 
-- serviceList2 = getServices $ 
--    noServices `addService` apacheInstalled `addService` torBridge 


main = startServices serviceList1 

नोट कैसे प्रत्येक सेवा के लिए बंदरगाहों प्रकार से परिभाषित किया जाता:

इस प्रयोग में कोड है। serviceList1httpService और apacheInstalled सेवा का उपयोग करता है। यह संकलित करता है, क्योंकि उनके बंदरगाह संघर्ष नहीं करते हैं। serviceList2 बाहर टिप्पणी की है, और इस संकलन त्रुटि अगर uncommented का कारण बनता है:

DefinedServices.hs:22:56: 
    Couldn't match type 'False with 'True 
    Expected type: 'True 
     Actual type: ServiceTypes.UniquePorts '[443, 80, 443] 
    In the second argument of `($)', namely 
     `noServices `addService` apacheInstalled `addService` torBridge' 
    In the expression: 
     getServices 
     $ noServices `addService` apacheInstalled `addService` torBridge 
    In an equation for `serviceList2': 
     serviceList2 
      = getServices 
      $ noServices `addService` apacheInstalled `addService` torBridge 
Failed, modules loaded: ServiceTypes. 

यह बहुत अच्छी तरह से समस्या का वर्णन करता: UniquePorts के रूप में 443 दो बार प्रयोग किया जाता है झूठी किया जा रहा समाप्त होता है।


तो यहाँ है कि यह कैसे ServiceTypes.hs में किया जाता है:

{-# LANGUAGE TypeOperators, PolyKinds, RankNTypes #-} 
{-# LANGUAGE KindSignatures, DataKinds, TypeFamilies, UndecidableInstances #-} 
module ServiceTypes (
    makeService, noServices, addService, Service, ServiceDetails(..) 
    , getServices, startServices) where 
import GHC.TypeLits 
import Control.Monad 
import Data.Type.Equality 
import Data.Type.Bool 

हम भाषा एक्सटेंशन की एक पूरी गुच्छा की जरूरत है इस काम करने के लिए मिलता है। इसके अलावा, सुरक्षित इंटरफ़ेस परिभाषित किया गया है।

सबसे पहले, यह जांचने के लिए कि एक सूची अद्वितीय है या नहीं, एक प्रकार के स्तर की फ़ंक्शन की आवश्यकता है। यह Data.Type.Equality और Data.Type.Bool में परिवार ऑपरेटरों के प्रकार का लाभ उठाता है। ध्यान दें कि निम्न कोड केवल टाइपशेकर द्वारा निष्पादित किया जाता है।

type family UniquePorts (list1 :: [Nat]) :: Bool 
type instance UniquePorts '[] = True 
type instance UniquePorts (a ': '[]) = True 
type instance UniquePorts (a ': b ': rest) = Not (a == b) && UniquePorts (a ': rest) && UniquePorts (b ': rest) 

यह अद्वितीय की एक पुनरावर्ती परिभाषा है।

इसके बाद, के रूप में हम एक साथ कई सेवाओं का उपयोग करेगा, वहाँ एक में दो सूचियों संयोजित करने का उपाय करने की आवश्यकता होगी:

type family Concat (list1 :: [a]) (list2 :: [a]) :: [a] 
type instance Concat '[] list2 = list2 
type instance Concat (a ': rest) list2 = a ': Concat rest list2 

सभी प्रकार के स्तर के कार्यों हम यह अपेक्षा है कि! एक भी सेवा की वास्तविक जानकारी के लिए,

data Service (ports :: [Nat]) service = Service service 

अगला:

इसके बाद, मैं एक Service प्रकार, कि जरूरत पोर्ट के साथ एक और प्रकार लपेटता परिभाषित करेगा।अब अंत में कई सेवाओं सूचियों के लिए

makeService :: String -> IO() -> Service ports ServiceDetails 
makeService name action = Service $ ServiceDetails name action 

: आप आप क्या जरूरत को यह अनुकूलित करना चाहिए:

data ServiceDetails = ServiceDetails { 
    serviceName :: String 
    , runService :: IO() 
    } 

मैं भी परिभाषित बंदरगाहों के साथ एक Service प्रकार में एक सेवा रैप करने के लिए एक सहायक समारोह गयी। जहां यह सब एक साथ आता है

noServices :: Service '[] [ServiceDetails] 
noServices = Service [] 

addService है:

addService :: (finalPorts ~ Concat ports newPorts, UniquePorts finalPorts ~ True) 
    => Service ports [ServiceDetails] 
    -> Service newPorts ServiceDetails 
    -> Service finalPorts [ServiceDetails] 
addService (Service serviceList) (Service newService) = 
    Service $ (newService : serviceList) 

finalPorts ~ Concat ports newPorts सूची में केवल finalPorts बंदरगाहों के संयोजन बनाता है `noServices बस सेवाओं की एक खाली सूची है, जो स्पष्ट रूप से कोई बंदरगाहों का उपयोग करता परिभाषित करता है सेवाओं और नई सेवा का। UniquePorts finalPorts ~ True सुनिश्चित करता है कि अंतिम बंदरगाहों में कोई डुप्लिकेट बंदरगाह नहीं है। बाकी का कार्य पूरी तरह से तुच्छ है।

getServices से [ServiceDetails] को अनचाहे करता है। Service कन्स्ट्रक्टर को सार्वजनिक नहीं किया गया है, Service ports [ServiceDetails] प्रकार बनाने का एकमात्र तरीका noServices और addService फ़ंक्शंस के माध्यम से सुरक्षित है, जो सुरक्षित होने की गारंटी है।

getServices :: Service ports [ServiceDetails] -> [ServiceDetails] 
getServices (Service details) = details 

अंत में सेवाएं चलाने के लिए एक परीक्षण के समारोह: इस नई कार्यक्षमता के लिए

startServices :: [ServiceDetails] -> IO() 
startServices services = forM_ services $ \service -> do 
    putStrLn $ "Starting service " ++ (serviceName service) 
    runService service 
    putStrLn "------" 

संभावनाएं लगभग अंतहीन हैं, और पिछले GHC संस्करणों के ऊपर एक विशाल सुधार (यह अभी भी संभव था , लेकिन अधिक अधिक कठिन)। एक बार जब आप प्रकारों में मूल्यों का उपयोग करके अपना सिर प्राप्त कर लेते हैं तो यह कोड बहुत आसान होता है।

+2

सिंटैक्स के बारे में कोई संदेह। 'सेवा '[443] सेवा विवरण' में' '' '' '' '' '' '' 'सेवा है, जबकि सेवा [80,443] सेवा विवरण नहीं है? – danidiaz

+1

सामान्य रूप से 'सामान्य प्रकारों और डेटा कन्स्ट्रक्टर के प्रचार के बीच संघर्ष को हल करने के लिए उपयोग किया जा सकता है। [443] सामान्य प्रकार के कन्स्ट्रक्टर [] प्रकार के स्तर 443 :: नाट के (बीमार प्रकार) आवेदन होगा। '[443] है' (:) 443 '[], जहां' [] और '(:) सूची प्रकार के डेटा कन्स्ट्रक्टर (:) और [] के प्रचार हैं। [80,443] को 'की आवश्यकता नहीं है' क्योंकि यह स्पष्ट नहीं है; कोई साधारण प्रकार लिखा नहीं है [ए, बी]। –

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