एक पूर्ण स्पष्टीकरण के लिए 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
इन प्रारंभिक तैयारियाँ के बाद, हम चलते रहने के लिए तैयार हैं।
एपीआई विनिर्देशों
पहले घटक डेटाटाइप्स कर रहे हैं कि एपीआई विनिर्देशों के लिए इस्तेमाल किया जा रहा परिभाषित करने के लिए है।
एक Get a
प्रतिनिधित्व करता है और प्रकार a
(की तरह *
) के अंतिम बिंदु:
data Get (a :: *)
data a :<|> b = a :<|> b
infixr 8 :<|>
data (a :: k) :> (b :: *)
infixr 9 :>
data Capture (a :: *)
हम अपने सरल भाषा में केवल चार निर्माणों परिभाषित करते हैं। पूर्ण नौकर के साथ तुलना में, हम यहां सामग्री प्रकारों को अनदेखा करते हैं। हमें केवल एपीआई विनिर्देशों के लिए डेटाटाइप की आवश्यकता है। अब सीधे संबंधित मान हैं, और इसलिए Get
के लिए कोई कन्स्ट्रक्टर नहीं है।
a :<|> b
के साथ, हम दो मार्गों के बीच चुनाव का प्रतिनिधित्व करते हैं। फिर, हमें एक निर्माता की आवश्यकता नहीं होगी, लेकिन यह पता चला है कि हम :<|>
का उपयोग कर API के हैंडलर का प्रतिनिधित्व करने के लिए हैंडलर की एक जोड़ी का उपयोग करेंगे। :<|>
के नेस्टेड अनुप्रयोगों के लिए, हमें हैंडलर के नेस्टेड जोड़े मिलेंगे, जो हास्केल में मानक नोटेशन का उपयोग करके कुछ हद तक बदसूरत लगते हैं, इसलिए हम :<|>
कन्स्ट्रक्टर को एक जोड़ी के बराबर समझाते हैं।
item :> rest
के साथ, हम नेस्ट का प्रतिनिधित्व करते हैं मार्गों, जहां item
पहला घटक है और rest
शेष घटक हैं। हमारे सरलीकृत डीएसएल में, item
के लिए केवल दो संभावनाएं हैं: एक प्रकार-स्तरीय स्ट्रिंग, या Capture
। क्योंकि प्रकार स्तरीय तार तरह Symbol
के हैं, लेकिन एक Capture
, नीचे परिभाषित तरह *
की है, हम :>
तरह-बहुरूपी का पहला तर्क करते हैं, तो यह है कि दोनों विकल्पों हास्केल तरह सिस्टम द्वारा स्वीकार कर रहे हैं।
एक 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)
आप पर [पेपर] एक नज़र था है (http://www.andres-loeh.de/Servant/servant-wgp.pdf) ? ... पता नहीं है कि हम उससे बेहतर स्पष्टीकरण प्राप्त कर सकते हैं ... हो सकता है कि आप इसे पढ़ लें और विस्तार से उन प्रश्नों के साथ वापस आएं जिन्हें आप समझ में नहीं आते हैं - यहां प्रश्न कम से कम व्यापक है क्योंकि पेपर लंबा है;) – Carsten
वर्ग 'जीएचसी। टाइपपेट्स। केनाउनसिंबोल' और संबंधित फ़ंक्शंस का उपयोग स्तरीय स्तर तारों ('प्रतीक') को वैल्यू लेवल स्ट्रिंग में कनवर्ट करने के लिए किया जाता है। तंत्र अनिवार्य रूप से किसी अन्य प्रकार के लिए समान है: एक प्रकार का वर्ग का उपयोग करें। अन्य प्रकारों के प्रकार उत्पन्न करने के लिए, आप एक प्रकार की कक्षा या एक प्रकार का परिवार का उपयोग कर सकते हैं। "कैसे" का सवाल काफी व्यापक है लेकिन यह छोटा संस्करण है। – user2407038
@ करस्टन ओह। मुझे नहीं पता था कि एक पेपर था। धन्यवाद :) – Ana