Haskell Language
टेम्प्लेट हास्केल और QuasiQuotes
खोज…
टिप्पणियों
टेम्पलेट हास्केल क्या है?
टेम्पलेट हास्केल जीएचसी हास्केल में निर्मित टेम्पलेट मेटा-प्रोग्रामिंग सुविधाओं को संदर्भित करता है। मूल कार्यान्वयन का वर्णन करने वाला कागज यहां पाया जा सकता है ।
चरण क्या हैं? (या, चरण प्रतिबंध क्या है?)
जब कोड निष्पादित किया जाता है, तो स्टेज का उल्लेख होता है। आम तौर पर, कोड को केवल रनटाइम पर निकाला जाता है, लेकिन टेम्पलेट हास्केल के साथ, कोड को संकलन समय पर निष्पादित किया जा सकता है। "सामान्य" कोड चरण 0 है और संकलन-समय कोड चरण 1 है।
चरण प्रतिबंध इस तथ्य को संदर्भित करता है कि चरण 1 पर एक चरण 0 कार्यक्रम निष्पादित नहीं किया जा सकता है - यह संकलन समय पर किसी भी नियमित कार्यक्रम (सिर्फ मेटा-प्रोग्राम) को चलाने में सक्षम होने के बराबर नहीं होगा।
कन्वेंशन (और कार्यान्वयन सादगी के लिए) के द्वारा, वर्तमान मॉड्यूल में कोड हमेशा चरण 0 होता है और अन्य सभी मॉड्यूल से आयातित कोड चरण 1 होता है। इस कारण से, अन्य मॉड्यूल से केवल अभिव्यक्त किए जा सकते हैं।
ध्यान दें कि एक चरण 1 कार्यक्रम एक चरण 0 प्रकार की अभिव्यक्ति है Q Exp
, Q Type
, आदि; लेकिन रूपांतरण सही नहीं है - प्रकार का प्रत्येक मान (चरण 0 प्रोग्राम) Q Exp
एक स्टेज 1 प्रोग्राम है;
फ़ुथर्मोर, चूंकि अवशेषों को घोंसला दिया जा सकता है, इसलिए पहचानकर्ताओं के पास चरण 1 से अधिक हो सकते हैं। चरण प्रतिबंध तब सामान्यीकृत किया जा सकता है - एक चरण n प्रोग्राम किसी भी चरण m> n में निष्पादित नहीं किया जा सकता है। उदाहरण के लिए, कोई व्यक्ति कुछ त्रुटि संदेशों में 1 से अधिक ऐसे चरणों का संदर्भ देख सकता है:
>:t [| \x -> $x |]
<interactive>:1:10: error:
* Stage error: `x' is bound at stage 2 but used at stage 1
* In the untyped splice: $x
In the Template Haskell quotation [| \ x -> $x |]
टेम्पलेट हास्केल का उपयोग असंबंधित पहचानकर्ताओं से नहीं-में-गुंजाइश त्रुटियों का कारण बनता है?
आम तौर पर, एकल हास्केल मॉड्यूल में सभी घोषणाओं को पारस्परिक रूप से पुनरावर्ती होने के रूप में सोचा जा सकता है। दूसरे शब्दों में, प्रत्येक शीर्ष-स्तरीय घोषणा एकल मॉड्यूल में हर दूसरे के दायरे में है। जब टेम्पलेट हास्केल सक्षम होता है, तो स्कूपिंग नियम बदल जाते हैं - मॉड्यूल को इसके बजाय TH splices द्वारा अलग किए गए कोड के समूहों में तोड़ दिया जाता है, और प्रत्येक समूह पारस्परिक रूप से पुनरावर्ती होता है, और आगे के सभी समूहों के दायरे में प्रत्येक समूह।
क्यू प्रकार
Q :: * -> *
Language.Haskell.TH.Syntax
में परिभाषित प्रकार का कंस्ट्रक्टर। Haskell.TH.Syntax एक अमूर्त प्रकार है जो संगणनाओं का प्रतिनिधित्व करता है, जिसमें मॉड्यूल के संकलन-समय वातावरण तक पहुंच होती है जिसमें संगणना चलती है। Q
प्रकार भी चर सबस्टेशन को संभालता है, जिसे टीएच द्वारा नाम कैप्चर कहा जाता है (और यहां चर्चा की गई है ।) सभी अवशेषों में कुछ X
लिए QX
टाइप है।
संकलन-समय के वातावरण में शामिल हैं:
- इन-स्कोप आइडेंटिफ़ायर और कहा आइडेंटिफ़ायर के बारे में जानकारी,
- कार्यों के प्रकार
- प्रकार और स्रोत डेटा कन्स्ट्रक्टर के प्रकार
- प्रकार की घोषणाओं का पूरा विनिर्देश (वर्ग, प्रकार परिवार)
- स्रोत कोड (लाइन, कॉलम, मॉड्यूल, पैकेज) में स्थान जहां ब्याह होता है
- कार्यों की फिक्सेस (GHC 7.10)
- सक्षम GHC एक्सटेंशन (GHC 8.0)
Q
प्रकार में नए नाम उत्पन्न करने की क्षमता भी है, फ़ंक्शन के साथ नया नाम newName :: String -> Q Name
। ध्यान दें कि नाम कहीं भी अंतर्निहित नहीं है, इसलिए उपयोगकर्ता को इसे स्वयं बांधना चाहिए, और इसलिए यह सुनिश्चित करना कि नाम का उपयोग अच्छी तरह से किया गया है, उपयोगकर्ता की जिम्मेदारी है।
Q
पास Functor,Monad,Applicative
लिए उदाहरण हैं और यह Q
मानों में हेरफेर करने के लिए मुख्य इंटरफ़ेस है, साथ में Language.Haskell.TH.Lib
में प्रदान किए गए हैं, जो TH के फॉर्म के हर निर्माता के लिए एक सहायक फ़ंक्शन को परिभाषित करते हैं:
LitE :: Lit -> Exp
litE :: Lit -> ExpQ
AppE :: Exp -> Exp -> Exp
appE :: ExpQ -> ExpQ -> ExpQ
ध्यान दें कि ExpQ
, TypeQ
, DecsQ
और PatQ
एएसटी प्रकार के लिए समानार्थक शब्द हैं जो आमतौर पर Q
प्रकार के अंदर संग्रहीत होते हैं।
TH लाइब्रेरी एक फ़ंक्शन runQ :: Quasi m => Q a -> ma
प्रदान करता है runQ :: Quasi m => Q a -> ma
, और एक उदाहरण है Quasi IO
, इसलिए ऐसा लगेगा कि Q
प्रकार केवल एक फैंसी IO
। हालाँकि, runQ :: Q a -> IO a
IO
क्रिया का उत्पादन करता है, जिसका किसी भी संकलन-समय के वातावरण तक पहुँच नहीं है - यह केवल वास्तविक Q
प्रकार में उपलब्ध है। अगर इस तरह के पर्यावरण तक पहुँचने की कोशिश की जा रही है तो ऐसे IO
कार्यवाहियों के दौरान विफल हो जाएंगे।
एक एन-एरिटी करी
परिचित
curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)
उदाहरण के लिए, समारोह को मनमानी धमनी के रूप में सामान्यीकृत किया जा सकता है:
curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e
हालांकि, हाथ से 2 (जैसे) 20 के ट्यूल के लिए ऐसे कार्यों को लिखना थकाऊ होगा (और इस तथ्य को अनदेखा करते हुए कि आपके कार्यक्रम में 20 टुपल्स की उपस्थिति लगभग निश्चित रूप से सिग्नल के मुद्दों को इंगित करती है जिसे रिकॉर्ड के साथ तय किया जाना चाहिए)।
हम मनमाने ढंग से n
लिए इस तरह के curryN
कार्यों का निर्माण करने के लिए टेम्पलेट हास्केल का उपयोग कर सकते हैं:
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM)
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural)
curryN :: Natural -> Q Exp
curryN
फ़ंक्शन एक प्राकृतिक संख्या लेता है, और हास्केल एएसटी के रूप में, उस curryN
के करी फ़ंक्शन का उत्पादन करता है।
curryN n = do
f <- newName "f"
xs <- replicateM (fromIntegral n) (newName "x")
पहले हम फ़ंक्शन के प्रत्येक तर्कों के लिए नए प्रकार के चर बनाते हैं - एक इनपुट फ़ंक्शन के लिए, और प्रत्येक फ़ंक्शन के लिए एक फ़ंक्शन के लिए कहा जाता है।
let args = map VarP (f:xs)
अभिव्यक्ति args
पैटर्न f x1 x2 .. xn
प्रतिनिधित्व करता है। ध्यान दें कि एक पैटर्न सेपरेटैक्टिक इकाई है - हम इसे एक ही पैटर्न ले सकते हैं और इसे एक लैम्ब्डा, या एक फ़ंक्शन बाइंडिंग, या यहां तक कि एलएचएस ऑफ लेट बाइंडिंग (जो एक त्रुटि होगी) में रख सकते हैं।
ntup = TupE (map VarE xs)
फ़ंक्शन को तर्कों के अनुक्रम से तर्क का निर्माण करना चाहिए, जो कि हमने यहां किया है। पैटर्न चर ( VarP
) और अभिव्यक्ति चर ( VarE
) के बीच अंतर पर ध्यान दें।
return $ LamE args (AppE (VarE f) ntup)
अंत में, हम जो उत्पादन करते हैं वह AST \f x1 x2 .. xn -> f (x1, x2, .. , xn)
।
हम इस फ़ंक्शन को कोटेशन और 'लिफ्टेड' कंस्ट्रक्टर का उपयोग करके भी लिख सकते हैं:
...
import Language.Haskell.TH.Lib
curryN' :: Natural -> ExpQ
curryN' n = do
f <- newName "f"
xs <- replicateM (fromIntegral n) (newName "x")
lamE (map varP (f:xs))
[| $(varE f) $(tupE (map varE xs)) |]
ध्यान दें कि कोटेशन सिंटैक्टिक रूप से मान्य होना चाहिए, इसलिए [| \ $(map varP (f:xs)) -> .. |]
अमान्य है, क्योंकि पैटर्न की एक 'सूची' घोषित करने के लिए नियमित हास्केल में कोई रास्ता नहीं है - ऊपर \ var -> ..
और \ var -> ..
रूप में व्याख्या की गई है PatQ
, अर्थात एकल प्रतिमान, पैटर्न की सूची नहीं होने की अपेक्षा की जाती है।
अंत में, हम इस TH फ़ंक्शन को GHCi में लोड कर सकते हैं:
>:set -XTemplateHaskell
>:t $(curryN 5)
$(curryN 5)
:: ((t1, t2, t3, t4, t5) -> t) -> t1 -> t2 -> t3 -> t4 -> t5 -> t
>$(curryN 5) (\(a,b,c,d,e) -> a+b+c+d+e) 1 2 3 4 5
15
इस उदाहरण को मुख्य रूप से यहाँ से अनुकूलित किया गया है ।
टेम्पलेट हास्केल और Quasiquotes का सिंटैक्स
टेम्प्लेट हास्केल को -XTemplateHaskell
GHC एक्सटेंशन द्वारा सक्षम किया गया है। यह विस्तार इस अनुभाग में सभी सिंटैक्टिक विशेषताओं को और विस्तृत करने में सक्षम बनाता है। टेम्प्लेट हास्केल पर पूरा विवरण उपयोगकर्ता गाइड द्वारा दिया गया है।
splices
एक स्प्लिस एक नई सिंटैक्टिक इकाई है जो टेम्पलेट हास्केल द्वारा सक्षम है, जिसे
$(...)
रूप में लिखा गया है, जहां(...)
कुछ अभिव्यक्ति है।$
और अभिव्यक्ति के पहले चरित्र के बीच एक स्थान नहीं होना चाहिए; और टेम्प्लेट हास्केल$
ऑपरेटर के पार्सिंग को ओवरराइड करता है - उदाहरण के लिएf$g
को सामान्य रूप से पार्स किया जाता है($) fg
जबकि टेम्प्लेट हास्केल सक्षम होने के साथ, इसे एक स्प्लिस के रूप में पार्स किया जाता है।जब शीर्ष स्तर पर एक ब्याह दिखाई देता है, तो
$
छोड़ा जा सकता है। इस मामले में, spliced अभिव्यक्ति पूरी रेखा है।एक ब्याह कोड का प्रतिनिधित्व करता है जो हास्केल एएसटी का उत्पादन करने के लिए संकलन समय पर चलाया जाता है, और एएसटी को हास्केल कोड के रूप में संकलित किया जाता है और कार्यक्रम में डाला जाता है।
विभाजन के स्थान पर प्रकट हो सकते हैं: अभिव्यक्ति, पैटर्न, प्रकार, और शीर्ष-स्तरीय घोषणाएं। प्रत्येक मामले में क्रमशः
Q [Decl]
Q Type
,Q [Decl]
Q Exp
,Q Pat
,Q Type
,Q [Decl]
। ध्यान दें कि घोषणा के अवशेष केवल शीर्ष स्तर पर दिखाई दे सकते हैं, जबकि अन्य क्रमशः अन्य भाव, पैटर्न या प्रकार के अंदर हो सकते हैं।
अभिव्यक्ति उद्धरण (नोट: एक QuasiQuotation नहीं )
एक अभिव्यक्ति उद्धरण एक नई वाक्य रचना इकाई है जिसे निम्न में से एक के रूप में लिखा गया है:
-
[e|..|]
या[|..|]
-..
एक अभिव्यक्ति है और उद्धरण मेंQ Exp
टाइप है; -
[p|..|]
-..
एक पैटर्न है और उद्धरण मेंQ Pat
; -
[t|..|]
-..
एक प्रकार है और उद्धरण में टाइपQ Type
; -
[d|..|]
- -..
घोषणाओं की एक सूची है और उद्धरण मेंQ [Dec]
टाइप है।
-
एक अभिव्यक्ति उद्धरण एक संकलन समय कार्यक्रम लेता है और उस कार्यक्रम द्वारा प्रतिनिधित्व एएसटी का उत्पादन करता है।
एक बंटवारे में एक मान का उपयोग (जैसे
\x -> [| x |]
) बिना किसी ब्याह के\x -> [| $(lift x) |]
_-\x -> [| $(lift x) |]
लिए वाक्यरचनात्मक चीनी से मेल खाता है\x -> [| x |]
\x -> [| $(lift x) |]
, जहांlift :: Lift t => t -> Q Exp
वर्ग से आता है
class Lift t where lift :: t -> Q Exp default lift :: Data t => t -> Q Exp
टाइप किए गए अवशेष और उद्धरण
पहले से उल्लेखित (अप्रकाशित) स्पाइस के लिए टाइप किए गए स्लाइस similair हैं, और
$$(..)
रूप में लिखे गए हैं, जहां(..)
एक अभिव्यक्ति है।अगर
e
में टाइपQ (TExp a)
तो$$e
में टाइपa
।टाइप किए गए उद्धरण फार्म लेते हैं
[||..||]
जहाँ..
एक प्रकार की अभिव्यक्तिa
? परिणामी उद्धरण मेंQ (TExp a)
टाइप है।टाइप की गई अभिव्यक्ति को
unType :: TExp a -> Exp
बदला जा सकता है:unType :: TExp a -> Exp
।
QuasiQuotes
QuasiQuotes अभिव्यक्ति उद्धरणों को सामान्यीकृत करता है - पहले, अभिव्यक्ति उद्धरण द्वारा उपयोग किए जाने वाले पार्सर एक निश्चित सेट (
e,p,t,d
) में से एक है, लेकिन QuasiQuotes एक कस्टम पार्सर को परिभाषित करने और संकलन समय पर कोड का उपयोग करने की अनुमति देता है। अर्ध-कोटेशन सभी समान संदर्भों में नियमित उद्धरण के रूप में दिखाई दे सकते हैं।एक अर्ध-उद्धरण को
[iden|...|]
रूप में लिखा गया है, जहाँiden
Language.Haskell.TH.Quote.QuasiQuoter
का एक पहचानकर्ता है।iden
एक
QuasiQuoter
बस चार पार्सर से बना होता है, प्रत्येक अलग-अलग संदर्भों के लिए जिसमें उद्धरण दिखाई दे सकते हैं:
data QuasiQuoter = QuasiQuoter { quoteExp :: String -> Q Exp, quotePat :: String -> Q Pat, quoteType :: String -> Q Type, quoteDec :: String -> Q [Dec] }
नाम
हास्केल पहचानकर्ताओं को
Language.Haskell.TH.Syntax.Name
प्रकार द्वारा दर्शाया जाता है। टेम्प्लेट हास्केल में हास्केल कार्यक्रमों का प्रतिनिधित्व करने वाले सार सिंटैक्स पेड़ों की पत्तियों का नाम है।एक पहचानकर्ता जो वर्तमान में स्कोप में है, उसे या तो एक नाम में बदल दिया जा सकता है:
'e
या'T
। पहले मामले में,e
को अभिव्यक्ति के दायरे में व्याख्यायित किया जाता है, जबकि दूसरे मामले मेंT
प्रकार के दायरे में होता है (यह याद करते हुए कि प्रकार और मूल्य निर्माणकर्ता हास्केल में अमीगिटी के बिना नाम साझा कर सकते हैं)।