2015-10-31 12 views
30

मैं बहुत परेशान हूं कि कैसे Servant जादू का उपयोग करने में सक्षम है कि यह टाइपिंग का उपयोग करता है। वेब साइट पर उदाहरण पहले से ही मुझे बहुत पहेली:नौकर के प्रकार-आधारित API को सक्षम करने के लिए उपयोग की जाने वाली सभी तंत्र क्या हैं?

type MyAPI = "date" :> Get '[JSON] Date 
     :<|> "time" :> Capture "tz" Timezone :> Get '[JSON] Time 

मैं "तिथि", "समय", [JSON] और "TZ" प्रकार स्तरीय शाब्दिक हैं मिलता है। वे मूल्य हैं जो "बनने" प्रकार हैं। ठीक है।

मुझे लगता है कि :> और :<|> टाइप ऑपरेटर हैं। ठीक है।

मुझे यह नहीं पता कि इन चीजों को कैसे बनने के बाद, मूल्यों में वापस निकाला जा सकता है। ऐसा करने के लिए तंत्र क्या है?

मैं भी कैसे इस प्रकार के पहले भाग हस्ताक्षर IO Date, या के एक समारोह की उम्मीद ढांचे प्राप्त कर सकते हैं नहीं मिलता है कि कैसे इस प्रकार के दूसरे भाग हस्ताक्षर Timezone -> IO Time के एक समारोह की उम्मीद ढांचे प्राप्त कर सकते हैं मुझ से। यह परिवर्तन कैसे होता है?

और फिर फ्रेमवर्क एक फ़ंक्शन को कैसे कॉल कर सकता है जिसके लिए इसे प्रारंभ में नहीं पता था?

मुझे यकीन है कि यहां कई जीएचसी एक्सटेंशन और अद्वितीय विशेषताएं हैं जो इस जादू को करने के लिए गठबंधन से परिचित नहीं हैं।

क्या कोई यह समझा सकता है कि यहां कौन सी विशेषताएं शामिल हैं और वे एक साथ कैसे काम कर रहे हैं?

+2

आप पर [पेपर] एक नज़र था है (http://www.andres-loeh.de/Servant/servant-wgp.pdf) ? ... पता नहीं है कि हम उससे बेहतर स्पष्टीकरण प्राप्त कर सकते हैं ... हो सकता है कि आप इसे पढ़ लें और विस्तार से उन प्रश्नों के साथ वापस आएं जिन्हें आप समझ में नहीं आते हैं - यहां प्रश्न कम से कम व्यापक है क्योंकि पेपर लंबा है;) – Carsten

+0

वर्ग 'जीएचसी। टाइपपेट्स। केनाउनसिंबोल' और संबंधित फ़ंक्शंस का उपयोग स्तरीय स्तर तारों ('प्रतीक') को वैल्यू लेवल स्ट्रिंग में कनवर्ट करने के लिए किया जाता है। तंत्र अनिवार्य रूप से किसी अन्य प्रकार के लिए समान है: एक प्रकार का वर्ग का उपयोग करें। अन्य प्रकारों के प्रकार उत्पन्न करने के लिए, आप एक प्रकार की कक्षा या एक प्रकार का परिवार का उपयोग कर सकते हैं। "कैसे" का सवाल काफी व्यापक है लेकिन यह छोटा संस्करण है। – user2407038

+0

@ करस्टन ओह। मुझे नहीं पता था कि एक पेपर था। धन्यवाद :) – Ana

उत्तर

34

एक पूर्ण स्पष्टीकरण के लिए Servant paper पर देखकर सबसे अच्छा विकल्प हो सकता है। फिर भी, मैं का संस्करण "TinyServant" लागू करके, Servant द्वारा लिया गया दृष्टिकोण को चित्रित करने का प्रयास करूंगा, नौकर न्यूनतम न्यूनतम तक कम हो गया है।

क्षमा करें कि यह उत्तर इतना लंबा है। हालांकि, यह अभी भी पेपर की तुलना में थोड़ा छोटा है, और यहां चर्चा की गई कोड "केवल" 81 लाइनें, हैस्केल फ़ाइल here के रूप में भी उपलब्ध है।

तैयारी

शुरू करने के लिए यहाँ भाषा एक्सटेंशन हम की आवश्यकता होगी:

{-# LANGUAGE DataKinds, PolyKinds, TypeOperators #-} 
{-# LANGUAGE TypeFamilies, FlexibleInstances, ScopedTypeVariables #-} 
{-# LANGUAGE InstanceSigs #-} 

पहले तीन प्रकार स्तरीय डीएसएल खुद की परिभाषा के लिए आवश्यक हैं। डीएसएल टाइप-स्तरीय स्ट्रिंग्स (DataKinds) का उपयोग करता है और दयालु बहुरूपता (PolyKinds) का उपयोग करता है। टाइप-लेवल इंफिक्स ऑपरेटरों जैसे :<|> और :> का उपयोग TypeOperators एक्सटेंशन की आवश्यकता है।

दूसरी तीन की व्याख्या की परिभाषा के लिए आवश्यक है (हम कुछ वेब सर्वर के बारे में याद दिलाते हैं, लेकिन पूरे वेब भाग के बिना )। इसके लिए, हमें टाइप-स्तरीय फ़ंक्शंस (TypeFamilies) की आवश्यकता है, कुछ प्रकार के क्लास प्रोग्रामिंग के लिए (FlexibleInstances) की आवश्यकता होगी, और चेकर को टाइप करने के लिए कुछ प्रकार की टिप्पणियांकी आवश्यकता होगी।

प्रलेखन उद्देश्यों के लिए शुद्ध रूप से, हम InstanceSigs का भी उपयोग करते हैं।

यहाँ हमारे मॉड्यूल शीर्ष लेख है:

module TinyServant where 

import Control.Applicative 
import GHC.TypeLits 
import Text.Read 
import Data.Time 

इन प्रारंभिक तैयारियाँ के बाद, हम चलते रहने के लिए तैयार हैं।

एपीआई विनिर्देशों

पहले घटक डेटाटाइप्स कर रहे हैं कि एपीआई विनिर्देशों के लिए इस्तेमाल किया जा रहा परिभाषित करने के लिए है।

  1. एक Get a प्रतिनिधित्व करता है और प्रकार a (की तरह *) के अंतिम बिंदु:

    data Get (a :: *) 
    
    data a :<|> b = a :<|> b 
    infixr 8 :<|> 
    
    data (a :: k) :> (b :: *) 
    infixr 9 :> 
    
    data Capture (a :: *) 
    

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

  2. a :<|> b के साथ, हम दो मार्गों के बीच चुनाव का प्रतिनिधित्व करते हैं। फिर, हमें एक निर्माता की आवश्यकता नहीं होगी, लेकिन यह पता चला है कि हम :<|> का उपयोग कर API के हैंडलर का प्रतिनिधित्व करने के लिए हैंडलर की एक जोड़ी का उपयोग करेंगे। :<|> के नेस्टेड अनुप्रयोगों के लिए, हमें हैंडलर के नेस्टेड जोड़े मिलेंगे, जो हास्केल में मानक नोटेशन का उपयोग करके कुछ हद तक बदसूरत लगते हैं, इसलिए हम :<|> कन्स्ट्रक्टर को एक जोड़ी के बराबर समझाते हैं।

  3. item :> rest के साथ, हम नेस्ट का प्रतिनिधित्व करते हैं मार्गों, जहां item पहला घटक है और rest शेष घटक हैं। हमारे सरलीकृत डीएसएल में, item के लिए केवल दो संभावनाएं हैं: एक प्रकार-स्तरीय स्ट्रिंग, या Capture। क्योंकि प्रकार स्तरीय तार तरह Symbol के हैं, लेकिन एक Capture, नीचे परिभाषित तरह * की है, हम :> तरह-बहुरूपी का पहला तर्क करते हैं, तो यह है कि दोनों विकल्पों हास्केल तरह सिस्टम द्वारा स्वीकार कर रहे हैं।

  4. एक Capture a एक मार्ग घटक है कि, पर कब्जा कर लिया पार्स और फिर प्रकार a की एक पैरामीटर के रूप में हैंडलर के संपर्क में है प्रतिनिधित्व करता है। पूर्ण नौकर में, Capture में पैरामीटर पैरामीटर के रूप में एक अतिरिक्त स्ट्रिंग है जिसका उपयोग प्रलेखन पीढ़ी के लिए किया जाता है। हम यहां स्ट्रिंग को छोड़ देते हैं।

उदाहरण API

अब हम सवाल से एपीआई विनिर्देशन का एक संस्करण, वास्तविक प्रकार Data.Time में उत्पन्न करने के लिए अनुकूलित, और हमारे सरलीकृत डीएसएल के लिए नीचे लिख सकते हैं:

type MyAPI = "date" :> Get Day 
     :<|> "time" :> Capture TimeZone :> Get ZonedTime 

सर्वर

के रूप में व्याख्या सबसे दिलचस्प पहलू निश्चित रूप से हम क्या कर सकते हैं एपीआई के साथ, और यह भी अधिकतर सवाल क्या है।

नौकर कई व्याख्याओं को परिभाषित करता है, लेकिन वे सभी समान पैटर्न का पालन करते हैं। हम यहां एक को परिभाषित करेंगे, जो वेब सर्वर के रूप में व्याख्या से प्रेरित है।

नौकर में, serve समारोह एपीआई प्रकार और एक WAI Application है, जो अनिवार्य रूप से प्रतिक्रियाओं को HTTP अनुरोध से एक समारोह है करने के लिए एपीआई प्रकार मिलान हैंडलर के लिए एक प्रॉक्सी लेता है। हम यहाँ वेब भाग से सार , और बदले

serve :: HasServer layout 
     => Proxy layout -> Server layout -> [String] -> IO String 

को परिभाषित करेंगे।

HasServer वर्ग, हम नीचे परिभाषित करेंगे जो, प्रकार स्तरीय डीएसएल के विभिन्न निर्माणों के लिए उदाहरणों है और इसलिए encodes क्या यह एक हास्केल प्रकार layout के लिए इसका मतलब है की एक API प्रकार के रूप में व्याख्या होने के लिए एक सर्वर।

Proxy प्रकार और मूल्य स्तर के बीच एक कनेक्शन बनाता है। यह

data Proxy a = Proxy 

रूप में परिभाषित किया गया है और इसकी एकमात्र उद्देश्य है कि एक स्पष्ट रूप से निर्दिष्ट प्रकार के साथ एक Proxy निर्माता में पास करके, हम इसे बहुत स्पष्ट क्या एपीआई प्रकार हम सर्वर गणना करने के लिए चाहते हैं के लिए कर सकते हैं।

Server तर्क API के लिए हैंडलर है। यहां, Server स्वयं एक प्रकार का परिवार है, और एपीआई प्रकार से गणना है कि हैंडलर के पास होना चाहिए। यह का एक मुख्य घटक है जो Servant को सही तरीके से काम करता है।

तारों की सूची अनुरोध का प्रतिनिधित्व करती है, यूआरएल घटकों की सूची में कम हो जाती है। नतीजतन, हम हमेशा String प्रतिक्रिया, लौटाते हैं और हम IO के उपयोग की अनुमति देते हैं। पूर्ण नौकर कुछ हद तक जटिल प्रकारों का उपयोग करता है, लेकिन विचार समान है।

Server प्रकार परिवार

हम पहले एक प्रकार परिवार के रूप में Server परिभाषित करते हैं। (नौकर में, वास्तविक प्रकार का परिवार इस्तेमाल किया जा रहा है ServerT है, और को HasServer कक्षा के हिस्से के रूप में परिभाषित किया गया है।)

type family Server layout :: * 

एक Get a endpoint के लिए हैंडलर बस एक IO कार्रवाई एक a उत्पादन है। (एक बार फिर, पूर्ण नौकर कोड में, हम इस तरह एक त्रुटि के उत्पादन के रूप में थोड़ा और अधिक विकल्प, की है।)

type instance Server (Get a) = IO a 

a :<|> b के लिए हैंडलर संचालकों की एक जोड़ी है, इसलिए हम निर्धारित कर सकते हैं

type instance Server (a :<|> b) = (Server a, Server b) -- preliminary 

लेकिन जैसे-जैसे ऊपर संकेत, :<|> की नेस्टेड घटनाओं के लिए इस नेस्टेड जोड़े, जो एक इन्फ़िक्स जोड़ी निर्माता के साथ कुछ हद तक अच्छे देखने के लिए ओर जाता है, तो नौकर बजाय बराबर

012,351,641 को परिभाषित करता है
type instance Server (a :<|> b) = Server a :<|> Server b 

यह बताने के लिए बनी हुई है कि प्रत्येक पथ घटकों को कैसे संभाला जाता है।

मार्गों में शाब्दिक तार हैंडलर के प्रकार को प्रभावित नहीं करते:

type instance Server ((s :: Symbol) :> r) = Server r 

एक पर कब्जा, हालांकि, इसका मतलब है कि हैंडलर उम्मीद प्रकार का एक अतिरिक्त तर्क पर कब्जा कर लिया जा रहा है:

type instance Server (Capture a :> r) = a -> Server r 

उदाहरण एपीआई

अगर हम Server MyAPI का विस्तार, हम OBT के हैंडलर प्रकार कम्प्यूटिंग ऐन

Server MyAPI ~ Server ("date" :> Get Day 
        :<|> "time" :> Capture TimeZone :> Get ZonedTime) 
      ~  Server ("date" :> Get Day) 
       :<|> Server ("time" :> Capture TimeZone :> Get ZonedTime) 
      ~  Server (Get Day) 
       :<|> Server ("time" :> Capture TimeZone :> Get ZonedTime) 
      ~  IO Day 
       :<|> Server ("time" :> Capture TimeZone :> Get ZonedTime) 
      ~  IO Day 
       :<|> Server (Capture TimeZone :> Get ZonedTime) 
      ~  IO Day 
       :<|> TimeZone -> Server (Get ZonedTime) 
      ~  IO Day 
       :<|> TimeZone -> IO ZonedTime 

तो के रूप में इरादा, हमारे एपीआई के लिए सर्वर, संचालकों की एक जोड़ी, एक है कि एक तारीख प्रदान करता है, और एक है कि, एक समय क्षेत्र को देखते हुए की आवश्यकता है एक समय प्रदान करता है। हम अभी इन परिभाषित कर सकते हैं:

handleDate :: IO Day 
handleDate = utctDay <$> getCurrentTime 

handleTime :: TimeZone -> IO ZonedTime 
handleTime tz = utcToZonedTime tz <$> getCurrentTime 

handleMyAPI :: Server MyAPI 
handleMyAPI = handleDate :<|> handleTime 

HasServer वर्ग

हम अभी भी HasServer वर्ग है, जो के रूप में इस प्रकार लग रहा है लागू करने के लिए है:

class HasServer layout where 
    route :: Proxy layout -> Server layout -> [String] -> Maybe (IO String) 

समारोह का कार्य route है लगभग serve की तरह। आंतरिक रूप से, हमें सही राउटर के आने वाले अनुरोध को प्रेषित करना होगा। :<|> के मामले में, इसका मतलब है कि हमें दो हैंडलर के बीच एक विकल्प बनाना है। हम यह विकल्प कैसे बना सकते हैं? Maybe लौटकर, route विफल होने के लिए एक आसान विकल्प है। (फिर से, पूर्ण नौकर कुछ और अधिक परिष्कृत है, और संस्करण 0.5 में बेहतर रूटिंग रणनीति होगी।)

एक बार जब हम route परिभाषित किया है, हम आसानी से serve शर्तों route की में परिभाषित कर सकते हैं:

serve :: HasServer layout 
     => Proxy layout -> Server layout -> [String] -> IO String 
serve p h xs = case route p h xs of 
    Nothing -> ioError (userError "404") 
    Just m -> m 

तो मार्गों में से कोई भी मेल खाते हैं, हम एक 404. साथ अन्यथा असफल, हम परिणाम लौट ।

HasServer उदाहरणों

एक Get अंत बिंदु के लिए, हम

type instance Server (Get a) = IO a 

परिभाषित तो हैंडलर एक आईओ एक a है, जो हम एक String में बदलने के लिए है उत्पादन कार्रवाई है। हम इस उद्देश्य के लिए show का उपयोग करते हैं। वास्तविक नौकर कार्यान्वयन में, इस रूपांतरण को सामग्री प्रकार मशीनरी द्वारा को संभाला जाता है, और आमतौर पर JSON या HTML पर एन्कोडिंग शामिल होगा।

instance Show a => HasServer (Get a) where 
    route :: Proxy (Get a) -> IO a -> [String] -> Maybe (IO String) 
    route _ handler [] = Just (show <$> handler) 
    route _ _  _ = Nothing 

हम कि समाप्ति बिंदु मिलान कर रहे हैं के बाद से ही, का अनुरोध की आवश्यकता होती है इस बिंदु पर खाली किया जाना है। यदि ऐसा नहीं है, तो यह मार्ग मैच नहीं है और हम Nothing लौटाते हैं। चुनाव अगले पर

आइए नज़र:

instance (HasServer a, HasServer b) => HasServer (a :<|> b) where 
    route :: Proxy (a :<|> b) -> (Server a :<|> Server b) -> [String] -> Maybe (IO String) 
    route _ (handlera :<|> handlerb) xs = 
     route (Proxy :: Proxy a) handlera xs 
    <|> route (Proxy :: Proxy b) handlerb xs 

यहाँ, हम संचालकों की एक जोड़ी मिलता है, और हम Maybe के लिए <|> का उपयोग दोनों की कोशिश करना।

शाब्दिक स्ट्रिंग के लिए क्या होता है?

instance (KnownSymbol s, HasServer r) => HasServer ((s :: Symbol) :> r) where 
    route :: Proxy (s :> r) -> Server r -> [String] -> Maybe (IO String) 
    route _ handler (x : xs) 
    | symbolVal (Proxy :: Proxy s) == x = route (Proxy :: Proxy r) handler xs 
    route _ _  _      = Nothing 

s :> r के लिए हैंडलर r के लिए हैंडलर के रूप में एक ही प्रकार के है। हमें अनुरोध रहित स्तर और मिलान-स्तर स्ट्रिंग के मान-स्तर समकक्ष से मेल खाने का पहला घटक होने की आवश्यकता है। हम मूल्य-स्तरीय स्ट्रिंग को symbolVal लागू करने वाले टाइप-स्तरीय स्ट्रिंग अक्षर से संबंधित मान-स्तरीय स्ट्रिंग प्राप्त करते हैं। इसके लिए, हमें KnownSymbol टाइप-स्तरीय स्ट्रिंग शाब्दिक पर बाधा की आवश्यकता है। लेकिन जीएचसी में सभी ठोस अक्षर स्वचालित रूप से KnownSymbol का एक उदाहरण हैं।

अंतिम मामले कैप्चर के लिए है:

instance (Read a, HasServer r) => HasServer (Capture a :> r) where 
    route :: Proxy (Capture a :> r) -> (a -> Server r) -> [String] -> Maybe (IO String) 
    route _ handler (x : xs) = do 
    a <- readMaybe x 
    route (Proxy :: Proxy r) (handler a) xs 
    route _ _  _  = Nothing 

इस मामले में, हम यह मान सकते हैं कि हमारे हैंडलर वास्तव में एक समारोह है कि एक a उम्मीद है। हमें अनुरोध के पहले घटक को को a के रूप में पारदर्शी होने की आवश्यकता है। यहां, हम Read का उपयोग करते हैं, जबकि नौकर में, हम सामग्री प्रकार मशीनरी का फिर से उपयोग करते हैं। अगर पढ़ना विफल रहता है, तो हम अनुरोध पर विचार नहीं करते हैं कि वह मेल न करे। अन्यथा, हम इसे हैंडलर को खिला सकते हैं और जारी रख सकते हैं।

सबकुछ का परीक्षण

अब हम कर चुके हैं।

हम है कि सब कुछ की पुष्टि कर सकते GHCi में काम करता है:

GHCi> serve (Proxy :: Proxy MyAPI) handleMyAPI ["time", "CET"] 
"2015-11-01 20:25:04.594003 CET" 
GHCi> serve (Proxy :: Proxy MyAPI) handleMyAPI ["time", "12"] 
*** Exception: user error (404) 
GHCi> serve (Proxy :: Proxy MyAPI) handleMyAPI ["date"] 
"2015-11-01" 
GHCi> serve (Proxy :: Proxy MyAPI) handleMyAPI [] 
*** Exception: user error (404) 
संबंधित मुद्दे

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