पास्कॉन को हास्केल में "लिप्यंतरण" करने का एक सीधा तरीका है। यह मोनैड ट्रांसफार्मर के चालाक उपयोग द्वारा किया जा सकता है, जो डरावना लगता है, लेकिन यह वास्तव में नहीं है। शुद्धता के कारण, आप शुद्धता के कारण, जब आप उत्परिवर्तनीय स्थिति (जैसे append
और pop
संचालन उत्परिवर्तन कर रहे हैं) या अपवादों का उपयोग करना चाहते हैं, तो आपको इसे थोड़ा और स्पष्ट बनाना होगा। चलो शीर्ष पर शुरू करते हैं।
parse :: String -> SchemeExpr
parse s = readFrom (tokenize s)
अजगर docstring ने कहा, "एक स्ट्रिंग से एक योजना अभिव्यक्ति पढ़ने के लिए", तो मैं बस प्रकार हस्ताक्षर() के रूप में इस एन्कोडिंग की स्वतंत्रता ले लिया। वह डॉकस्ट्रिंग अप्रचलित हो जाता है क्योंकि प्रकार एक ही जानकारी को व्यक्त करता है। अब ... SchemeExpr
है? आपके कोड के अनुसार, एक योजना अभिव्यक्ति एक int, float, प्रतीक, या योजना अभिव्यक्तियों की सूची हो सकती है। चलिए एक डेटा प्रकार बनाते हैं जो इन विकल्पों का प्रतिनिधित्व करता है।
data SchemeExpr
= SInt Int
| SFloat Float
| SSymbol String
| SList [SchemeExpr]
deriving (Eq, Show)
आदेश हास्केल बताने के लिए कि Int
हम साथ एक SchemeExpr
के रूप में व्यवहार किया जाना चाहिए काम कर रहे हैं, हम SInt
के साथ टैग की जरूरत है। इसी तरह अन्य संभावनाओं के साथ। आइए tokenize
पर जाएं।
tokenize :: String -> [Token]
फिर, docstring एक प्रकार हस्ताक्षर में बदल जाता है: Token
रों की एक सूची में एक String
बदल जाते हैं। खैर, टोकन क्या है? यदि आप कोड को देखते हैं, तो आप देखेंगे कि बाएं और दाएं पैर के पात्र स्पष्ट रूप से विशेष टोकन हैं, जो विशेष व्यवहार को संकेत देते हैं। और कुछ भी है ... अनन्य। जबकि हम अन्य टोकन से माता-पिता को अधिक स्पष्ट रूप से अलग करने के लिए डेटा प्रकार बना सकते हैं, चलिए मूल पायथन कोड के करीब थोड़ा चिपकने के लिए स्ट्रिंग का उपयोग करें।
type Token = String
अब tokenize
लिखने का प्रयास करें। सबसे पहले, चलिए चेनिंग चेनिंग को पाइथन की तरह थोड़ा और देखने के लिए एक त्वरित छोटा ऑपरेटर लिखते हैं। हास्केल में, आप अपने स्वयं के ऑपरेटरों को परिभाषित कर सकते हैं।
(|>) :: a -> (a -> b) -> b
x |> f = f x
tokenize s = s |> replace "(" " ("
|> replace ")" ") "
|> words
words
split
की हास्केल का संस्करण है। हालांकि, हास्केल में replace
का कोई पूर्व-पका हुआ संस्करण नहीं है जिसे मैं जानता हूं। आप splitOn
और intercalate
के लिये दस्तावेज पढ़ें
-- add imports to top of file
import Data.List.Split (splitOn)
import Data.List (intercalate)
replace :: String -> String -> String -> String
replace old new s = s |> splitOn old
|> intercalate new
, इस सरल एल्गोरिदम सही समझ बनाने चाहिए: यहाँ एक है कि चाल करना चाहिए। हास्केलर्स आमतौर पर इसे replace old new = intercalate new . splitOn old
के रूप में लिखेंगे, लेकिन मैंने आसानी से पाइथन दर्शकों की समझ के लिए |>
का उपयोग किया था।
ध्यान दें कि replace
तीन तर्क लेता है, लेकिन ऊपर मैंने केवल दो के साथ इसका आह्वान किया है। हास्केल में आप आंशिक रूप से किसी भी फ़ंक्शन को लागू कर सकते हैं, जो कि बहुत साफ है। |>
यूनिक्स पाइप की तरह काम करता है, अगर आप अधिक प्रकार की सुरक्षा को छोड़कर नहीं बता सकते हैं।
अभी भी मेरे साथ? आइए atom
पर जाएं। वह घोंसला तर्क थोड़ा बदसूरत है, इसलिए चलो इसे साफ करने के लिए थोड़ा अलग दृष्टिकोण आज़माएं। हम Either
प्रकार का उपयोग बहुत अच्छी प्रस्तुति के लिए करेंगे।
atom :: Token -> SchemeExpr
atom s = Left s |> tryReadInto SInt
|> tryReadInto SFloat
|> orElse (SSymbol s)
हास्केल automagical coersion कार्यों int
और float
नहीं है, तो बजाय हम tryReadInto
का निर्माण करेगा। यहां बताया गया है कि यह कैसे काम करता है: हम Either
मूल्यों के आसपास धागे पर जा रहे हैं। एक Either
मान या तो Left
या Right
है। परंपरागत रूप से, Left
त्रुटि या विफलता को सिग्नल करने के लिए प्रयोग किया जाता है, जबकि Right
सफलता या समापन संकेत करता है। हास्केल में, पायथन-एस्क्यू फ़ंक्शन कॉल चेनिंग अनुकरण करने के लिए, आप केवल "स्वयं" तर्क को अंतिम के रूप में रखते हैं।
tryReadInto :: Read a => (a -> b) -> Either String b -> Either String b
tryReadInto f (Right x) = Right x
tryReadInto f (Left s) = case readMay s of
Just x -> Right (f x)
Nothing -> Left s
orElse :: a -> Either err a -> a
orElse a (Left _) = a
orElse _ (Right a) = a
tryReadInto
निर्धारित करने के लिए जो टाइप इसे में स्ट्रिंग पार्स करने का प्रयास कर रहा है में प्रकार निष्कर्ष पर निर्भर करता है। यदि पार्स विफल रहता है, तो यह Left
स्थिति में समान स्ट्रिंग को पुन: उत्पन्न करता है। यदि यह सफल होता है, तो यह वही कार्य करता है जो कार्य करता है और परिणाम Right
स्थिति में रखता है। orElse
हमें पूर्व गणना विफल होने के मामले में मूल्य प्रदान करके Either
को समाप्त करने की अनुमति देता है। क्या आप देख सकते हैं कि Either
यहां अपवादों के प्रतिस्थापन के रूप में कार्य करता है? चूंकि पाइथन कोड में ValueException
एस हमेशा कार्य के अंदर पकड़े गए हैं, हम जानते हैं कि atom
कभी भी को अपवाद नहीं उठाएगा। इसी प्रकार, हास्केल कोड में, भले ही हमने फ़ंक्शन के अंदर Either
का उपयोग किया, फिर भी इंटरफ़ेस जिसे हम बेनकाब करते हैं वह शुद्ध है: Token -> SchemeExpr
, कोई बाहरी रूप से दिखाई देने वाले दुष्प्रभाव नहीं।
ठीक है, चलो read_from
पर जाएं। सबसे पहले, खुद से सवाल पूछें: इस समारोह के किन दुष्प्रभाव हैं? यह tokens
pop
के माध्यम से अपने तर्क को बदलता है, और इसमें L
नाम की सूची में आंतरिक उत्परिवर्तन है। यह SyntaxError
अपवाद भी उठाता है। इस बिंदु पर, अधिकांश हास्केलर्स अपने हाथों को फेंक देंगे "ओह नोस! साइड इफेक्ट्स! सकल!" लेकिन सच्चाई यह है कि हास्केलर हर समय साइड इफेक्ट्स का भी उपयोग करते हैं। हम लोगों को डराने और हर कीमत पर सफलता से बचने के लिए बस उन्हें "मोनैड" कहते हैं। उत्परिवर्तन State
मोनैड के साथ पूरा किया जा सकता है, और Either
मोनड (आश्चर्य!) के साथ अपवाद। हम दोनों एक ही समय में उपयोग करना चाहते हैं, इसलिए हम वास्तव में "मोनैड ट्रांसफार्मर" का उपयोग करेंगे, जिसे मैं थोड़ा सा समझाऊंगा। यह नहीं है कि डरावना है, एक बार जब आप क्रुफ्ट को देखना सीखते हैं।
सबसे पहले, कुछ उपयोगिताओं। ये कुछ साधारण नलसाजी संचालन हैं। raise
हमें पायथन में "अपवाद उठाने" देगा, और whileM
हमें पायथन में थोड़ी देर के लूप लिखने देगा। उत्तरार्द्ध के लिए, हमें बस यह स्पष्ट करना होगा कि प्रभावों के किस क्रम में होना चाहिए: पहले स्थिति की गणना करने के लिए प्रभाव का प्रदर्शन करें, फिर यदि यह True
है, तो शरीर और लूप के प्रभावों को फिर से करें।
import Control.Monad.Trans.State
import Control.Monad.Trans.Class (lift)
raise = lift . Left
whileM :: Monad m => m Bool -> m() -> m()
whileM mb m = do
b <- mb
if b
then m >> whileM mb m
else return()
हम फिर से एक शुद्ध इंटरफ़ेस का पर्दाफाश करना चाहते हैं।हालांकि, एक मौका है कि SyntaxError
होगा, इसलिए हम प्रकार हस्ताक्षर में इंगित करेंगे कि परिणाम या तोSchemeExpr
या SyntaxError
होगा। यह याद दिलाता है कि जावा में कैसे आप एनोटेट कर सकते हैं कि एक विधि कौन सा अपवाद उठाएगी। ध्यान दें कि parse
के प्रकार हस्ताक्षर को भी बदलना है, क्योंकि यह SyntaxError को बढ़ा सकता है।
data SyntaxError = SyntaxError String
deriving (Show)
parse :: String -> Either SyntaxError SchemeExpr
readFrom :: [Token] -> Either SyntaxError SchemeExpr
readFrom = evalStateT readFrom'
हम इस टोकन सूची है कि में पारित हो जाता है पर एक स्टेटफुल गणना प्रदर्शन करने के लिए जा रहे हैं। अजगर के विपरीत, तथापि, हम नहीं फोन करने वाले के लिए कठोर हो सकता है और बहुत सूची हमें पारित कर दिया उत्परिवर्तित जा रहे हैं। इसके बजाए, हम अपनी खुद की राज्य की जगह स्थापित करेंगे और इसे टोकन सूची में शुरू करेंगे। हम do
नोटेशन का उपयोग करेंगे, जो यह दिखने के लिए सिंटैक्टिक चीनी प्रदान करता है जैसे हम प्रोग्रामिंग अनिवार्य रूप से कर रहे हैं। StateT
मोनैड ट्रांसफार्मर हमें get
, put
, और modify
राज्य संचालन देता है।
readFrom' :: StateT [Token] (Either SyntaxError) SchemeExpr
readFrom' = do
tokens <- get
case tokens of
[] -> raise (SyntaxError "unexpected EOF while reading")
(token:tokens') -> do
put tokens' -- here we overwrite the state with the "rest" of the tokens
case token of
"(" -> (SList . reverse) `fmap` execStateT readWithList []
")" -> raise (SyntaxError "unexpected close paren")
_ -> return (atom token)
मैं बाहर विभाजित कर दिया है कोड, का एक अलग हिस्सा में readWithList
भाग क्योंकि मैं तुम्हें प्रकार हस्ताक्षर देखना चाहते हैं। कोड का यह भाग नया स्कोप प्रस्तुत करता है, इसलिए हम मोनैड स्टैक के शीर्ष पर बस StateT
लेयर करते हैं जो हमारे पास पहले था। अब, get
, put
, और modify
संचालन पाइथन कोड में L
नामक चीज़ पर देखें। यदि हम tokens
पर इन परिचालनों को निष्पादित करना चाहते हैं, तो हम मोनैड स्टैक की एक परत को दूर करने के लिए क्रम में lift
के साथ ऑपरेशन को आसानी से पेश कर सकते हैं।
readWithList :: StateT [SchemeExpr] (StateT [Token] (Either SyntaxError))()
readWithList = do
whileM ((\toks -> toks !! 0 /= ")") `fmap` lift get) $ do
innerExpr <- lift readFrom'
modify (innerExpr:)
lift $ modify (drop 1) -- this seems to be missing from the Python
हास्केल में, एक सूची के अंत में जोड़कर अक्षम है, तो मैं बजाय prepended, और फिर बाद में सूची को उलट दिया। यदि आप प्रदर्शन में रूचि रखते हैं, तो बेहतर सूची-जैसी डेटा संरचनाएं आप उपयोग कर सकते हैं।
यहाँ पूरी फाइल है: http://hpaste.org/77852
तो अगर आप हास्केल के लिए नए हैं, तो यह शायद भयानक लग रहा है। मेरी सलाह है कि इसे कुछ समय दें। मोनाड अमूर्तता लगभग उतना ही डरावना नहीं है जितना लोग इसे बाहर करते हैं। आपको सिर्फ यह सीखना है कि अधिकांश भाषाओं में क्या है (उत्परिवर्तन, अपवाद, आदि), हास्केल पुस्तकालयों के माध्यम से प्रदान करता है। हास्केल में, आपको स्पष्ट रूप से निर्दिष्ट करना होगा कि आप कौन से प्रभाव चाहते हैं, और उन प्रभावों को नियंत्रित करना थोड़ा कम सुविधाजनक है। बदले में, हालांकि, हास्केल अधिक सुरक्षा प्रदान करता है ताकि आप गलती से गलत प्रभाव और अधिक शक्ति को मिश्रित न करें, क्योंकि आप प्रभाव को गठबंधन और पुन: सक्रिय करने के तरीके पर पूर्ण नियंत्रण में हैं।
आप [24 घंटे में अपने आप को लिखें योजना] को देखने के लिए चाहते हो सकता है (http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Parsing) अगर आप इस परियोजना को आगे ले जाना चाहते हैं। – AndrewC
ठीक है, आपने क्या प्रयास किया है? – Marcin
+1 मैं इस बारे में ब्लॉग करने के लिए एक अच्छा बहाना ढूंढ रहा हूं। मुझे लगता है कि मैं इसे सिर्फ एक SO उत्तर के रूप में लिखूंगा। –