4 فيبروري 2018 تي پوسٽ ڪيلٽيگ: haskell ، پروگرامن ، monads
هاسڪل ۾ ’مونادڪ پارسينگ‘ هڪ نن aو مقالو آهي ، جيڪو پارسيڪ ۽ اٽيپرسيڪ وانگر لائبريرين لاءِ بنيادي بنياد رکي ٿو. جيتوڻيڪ اهو 1998 ۾ شايع ٿيو هو (تقريبن 20 سال اڳ!) اهو عمر خشڪ نموني سان ڳري چڪو آهي ۽ ڪوڊ جا نمونا تقريباً ڪنهن به تبديلي آڻڻ سان هلندو. جيتوڻيڪ ، هن وقت کان وٺي اسٽيٽ آف اسٽيٽ ترقي ڪئي آهي ۽ منهنجو خيال آهي ته جديد هاسڪل جو استعمال انهي مواد کي پيروي ۽ لاڳو ڪرڻ ۾ آسان بڻائي سگهي ٿو.
هاسيل ۾ مونادڪ پارسنگ اها آهي جيڪا مون ٽنهي تي وڪڻي. Haskell کان اڳ تصريف سان منهنجي تجربا lexers ۽ جيان wrangling اوزار لاء شامل بگي regexes هئا bison
۽ flex
، ۽ باقي مون ٻڌو هوندي ته Haskell تصريف لاء سٺو هو مون کي ڏسي نه سگهيو هن اهو کڻي ڪيئن به ٿي سگهي ٿو جڏهن مون کي ڪنهن به مضبوط regex نه ڏسي سگهي لائبريريون! ڪجهه دستاويزن ۾ هڪ طرف مون کي Attoparsec ڏانهن اشارو ڪيو ۽ جڏهن مون ڏٺو ته RFC2616 پارسر مثال طور اهو جادو ٽڪري وانگر لڳي رهيو آهي. اهو ڪيترو نن smallڙو ٿي سگهي ٿو؟ ڪجهه هفتن ان جي ڪوشش ڪرڻ کان پوءِ آئون پاڻ مڃي ويو هوس ۽ مون ڪڏهن به پوئتي نه ڏٺو آهي. مونادن جي اها پهرين درخواست هئي مون سان ملاقات ڪئي جيڪا اصل ۾ منهنجي زندگي کي آسان بڻائي ٿي ، ۽ مون اهو محسوس ڪرڻ شروع ڪيو ته مونگ کان وڌيڪ مونجهات هئي ۽ نئين ايندڙن تائين ناقابل رسائي ٿيڻ جي ڪري.
پهرين تبديلي جيڪا آئون ڪرڻ ٿي چاهيو اها قسم جي تعريف آهي. ڪاغذ استعمال ڪندو آهي قسم
newtype Parser a = Parser (String -> [(a,String)])
۽ جيتوڻيڪ اها ڪافي مشهور تعريف آهي ته ان جي پنهنجي شاعريءَ آهي ، منهنجي خيال ۾ فهرستن جي لچڪ هتي ضايع ٿي وئي آهي. ليکڪ ان کي استعمال نه ڪندا آهن ، ۽ بدران هڪ ‘تعصب پسند انتخاب’ آپريٽر جي تعريف (+++)
ڪندا آهن جيڪي وڌ ۾ وڌ هڪ نتيجو ڏيندو آهي ۽ انهي جي بدران هر جاءِ تي استعمال ڪندو آهي. هاسيل ۾ اڳ ۾ ئي هڪ مڪمل طور تي بهتر ڊيٽا ڊيٽابيس موجود آهي ، گهڻن ئي عنصرن جي فهرستن جي لاءِ Maybe
، تنهنڪري آئون ان بدران استعمال ڪندس []
:
newtype Parser a = Parser (String -> Maybe (a, String))
اسان رکجي ته String
ڪرڻ s
۽ Maybe
ڪرڻ m
، هڪ کان وڌيڪ دلچسپ طرز وحي ڪيو ويندو آھي:
newtype Parser s m a = Parser (s -> m (a, s))
هي آهي StateT
! هن نموني کي سڃاڻڻ مثال جي وضاحت ڏا easierي آسان ڪري ٿي ، انهي حقيقت ۾ تمام گهڻو آسان آهي ته GHC اهو اسان سان ڪري سگهي ٿو پاڻ سان -XGeneralizedNewtypeDeriving
! مڪمليت لاءِ مان ان ڪم جي لالچ ۾ مزاحمت ڪندس ، پر توهان انهي سان پاڻ آزمائي سگهو ٿا
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Parser a = Parser (StateT String Maybe a) deriving (Functor, Applicative, Alternative, Monad)
ٻي تبديلي مڪمليت لاءِ پڻ آهي: ليکڪ سڌي طرح Monad
مثال ۾ گهڙي ڌار ٿا سمجهن Functor
۽ Applicative
پهرين. صحيح هجڻ جي ڪري ، Applicative
خلاصو اڃا تائين دريافت نه ڪيو ويو هو ، ۽ اهو پڻ سبب آهي جو ليکڪ عام طور تي وضاحت ڪن ٿا mzero
۽ mplus
(جنهن کي اهي سڏين ٿا (++)
) بدران وڌيڪ عام Alternative
طريقن empty
۽ (<|>)
. اسان جي Maybe
تبديلي جي ، تعريف Alternative
جو مطلب آهي ته مون کي انهن سان تنگ ڪرڻ جي ضرورت نه هوندي (+++)
.
آخر ۾ ، آئون ڪوشش ڪندس جتي ممڪن نه هجي وڌيڪ اطلاق واري انداز جي حق ۾ ، جتي استعمال <*>
ڪري سگهجي ، (جنهن کي ظاهر ڪري سگهجي ٿو ’اسپلٽ‘ جيڪڏهن توهان وٽ هن لاءِ اڳ ئي نالو ناهي) ڇاڪاڻ ته انهن اڪثر گهڻن اهو ان جي ضرورت آهي.
اچو ته شروع ڪريون!
{-# LANGUAGE InstanceSigs #-}
import Control.Applicative (Alternative(..))
import Control.Monad.Trans.State.Strict
import Control.Monad (guard)
import Data.Char (isSpace, isDigit, ord)
سهولت لاءِ مان هڪ تعريف ڪئي آهي unParser
جنهن Parser a
کي پنهنجي هيٺاهين تائين نه پهچايو آهي StateT String Maybe a
.
newtype Parser a = Parser { unParser :: StateT String Maybe a }
runParser = runStateT . unParser
fmap
ايترو ئي آسان آهي جيترو ڪتب آڻڻ Parser
۽ هيٺيان استعمال ڪرڻ وارن کي استعمال ڪرڻ StateT
آهي fmap
.
instance Functor Parser where
fmap :: (a -> b) -> Parser a -> Parser b
fmap f p = Parser $ f <$> unParser p
لاءِ وڌيڪ ناپسنديده Applicative
۽ Alternative
.
هن Alternative
typeclass اسان کي هڪ parser يا ٻئي parser ڊوڙندو جو خيال ظاهر ڪرڻ لاء، پهرين ڪامياب ڪمپوز نتيجي ۾ اجازت ڏئي ٿو. empty
اهو معاملو سنڀاليندو آهي جتي ٻئي پيرز ناڪام ٿيا ، ۽ (<|>)
(جنهن کي ‘alt ’ظاھر ڪري سگهجي ٿو) متبادل انجام ڏئي ٿو. هن پنهنجي تي آسان ڪافي آهي، پر Alternative
پڻ مهيا ڪري many
۽ some
جنهن کي انهيء چونڊجندڙ many
۽ many1
ڪاغذ کان:
-- many v = some v <|> pure []
-- some v = (:) <$> v <*> many v
پر صرف انهي []
سان بدلي ڪرڻ کانپوءِ Maybe
مون هتي ڪيو آهي جيڪو انهي (<|>)
سان مطابقت رکي ٿو (+++)
.
instance Applicative Parser where
pure :: a -> Parser a
pure a = Parser $ pure a
(<*>) :: Parser (a -> b) -> Parser a -> Parser b
f <*> a = Parser $ unParser f <*> unParser a
instance Alternative Parser where
empty :: Parser a
empty = Parser empty
(<|>) :: Parser a -> Parser a -> Parser a
a <|> b = Parser $ unParser a <|> unParser b
هي Monad
تعريف ڪجهه وڌيڪ دلچسپ آهي ، ڇاڪاڻ ته اسان کي هٿرادو طور ٺاهڻ گهرجي StateT
قيمت ، پر اهو پڻ ٻيهر ختم ڪرڻ ۽ بي ترتيب ڪرڻ تي.
instance Monad Parser where
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
a >>= f = Parser $ StateT $ \s -> do
(a', s') <- runParser a s
runParser (f a') s'
نوٽ اهو ته anyChar
صرف اهو ئي ڪم آهي جيڪو دستي طور تي هڪ ٺاهيندو آهي Parser
، ۽ satisfy
صرف هڪ ئي آهي جيڪو Monad
انٽرفيس جي ضرورت آهي .
anyChar :: Parser Char
anyChar = Parser . StateT $ \s -> case s of
[] -> empty
(c:cs) -> pure (c, cs)
satisfy :: (Char -> Bool) -> Parser Char
satisfy pred = do
c <- anyChar
guard $ pred c
pure c
char :: Char -> Parser Char
char = satisfy . (==)
string :: String -> Parser String
string [] = pure []
string (c:cs) = (:) <$> char c <*> string cs
ٻيهر ، many
۽ many1
وضاحت ڪرڻ جي ضرورت نه آهي ڇاڪاڻ ته اهي مفت مهيا ڪيا ويا آهن!
sepBy :: Parser a -> Parser b -> Parser [a]
sepBy p sep = (p `sepBy1` sep) <|> pure []
sepBy1 :: Parser a -> Parser b -> Parser [a]
sepBy1 p sep = (:) <$> p <*> many (sep *> p)
اهي ڪاغذ ۾ ڏنل تعريفن وانگر تقريبن ساڳيون آهن. مون مڪمليت chainr
لاءِ شامل ڪيو آهي.
chainl :: Parser a -> Parser (a -> a -> a) -> a -> Parser a
chainl p op a = (p `chainl1` op) <|> pure a
chainl1 :: Parser a -> Parser (a -> a -> a) -> Parser a
chainl1 p op = p >>= rest
where
rest a = (do
f <- op
b <- p
rest (f a b)) <|> pure a
chainr :: Parser a -> Parser (a -> a -> a) -> a -> Parser a
chainr p op a = (p `chainr1` op) <|> pure a
chainr1 :: Parser a -> Parser (a -> a -> a) -> Parser a
chainr1 p op = scan
where
scan = p >>= rest
rest a = (do
f <- op
b <- scan
rest (f a b)) <|> pure a
هتي فقط فرق آهي متبادل (>>)
سان (*>)
. اهي ساڳيا اثر آهن ، سواءِ ان جي ته اها بعد ۾ ڪم ڪري ٿي Applicative
۽ هڪ هم منصب سان به اچي (<*)
. جڏهن پارڪر لکندو آهيان ته مون کي اهو خاص طور تي ڪارائتو ثابت ٿيندو آهي ڇاڪاڻ ته اهي مون کي گهڻن parsers کي گڏ ڪرڻ جي اجازت ڏيندا آهن جڏهن آئون صرف انهن مان هڪ جي پيداوار بابت پرواهه ڪندا ignored *> ignored *> value <* ignored
. ڪيلڪيولر مثال هن ۾ استعمال ڪندو آهي factor
.
space :: Parser String
space = many (satisfy isSpace)
token :: Parser a -> Parser a
token p = p <* space
symbol :: String -> Parser String
symbol = token . string
apply :: Parser a -> String -> Maybe (a, String)
apply p = runParser (space *> p)
ڳڻپيوڪر جو مثال تقريباً اڻ ٽر آھي
expr, term, factor, digit :: Parser Int
expr = term `chainl1` addop
term = factor `chainl1` mulop
factor = digit <|> (symbol "(" *> expr <* symbol ")")
digit = subtract (ord '0') . ord <$> token (satisfy isDigit)
addop, mulop :: Parser (Int -> Int -> Int)
addop = (symbol "+" *> pure (+)) <|> (symbol "-" *> pure (-))
mulop = (symbol "*" *> pure (*)) <|> (symbol "/" *> pure (div))
آخرڪار ، معاوضي!
runParser expr "(1 + 2 * 4) / 3 + 5"
Just (8,"")
اسان 20 سالن ۾ ڇا حاصل ڪيو آهي؟ صرف نن changesن تبديلين سان ، ڪوڊ وڌيڪ ٺوس آهي ۽ بهتر ڪيل تجريدي استعمال ڪندو آهي. مثال طور، جيڪڏهن اسان کي هٽائي جي باري ۾ اسان جي ذهن تبديل []
سان Maybe
، اسان کي ان کي واپس صفا ڪري سگهن ٿا ۽ رڳو جي قسم جي دستخط تازه ڪاري ڪرڻ آهي ها apply
:
apply :: Parser a -> String -> [(a, String)]
apply p = runParser (space *> p) -- the implementation stays the same!
جيڪڏهن اسان بهتر غلط پيغام چاهيون ٿا ، اسان هڪ قسم استعمال ڪري سگهون ٿا جهڙوڪ Either String
جڳهن ۽ غلط پيغامن جي چڪاس لاءِ. هن yoctoparsec
لائبريري اوھان وٽ رهڻ پنهنجو وهڪرو قسم چونڊيو کي به وڌيڪ هن لڳن ٿا،.
ٻيو وڏو فرق Applicative
افعال جو خانداني هوندو آهي ، جنهن کي اسين جڏهن به اڳئين ترتيب واري قيمت تي شاخ هڻڻ جي ضرورت نه هئڻ تي اثر وجھي سگھون ٿا (جيڪو حيران ڪندڙ گهڻو ڪري اچڻو پوي ٿو). مون کي x <$> y <*> z
۽ وڏين پرستن جو مان ڏا fanو پرستار آهيان ignored *> value <* ignored
۽ منهنجو خيال آهي ته اهو ضروري آهي ته اسان ان طريقي سان پار ڪيو وڃي.
ٻي صورت ۾ ، ڪوڊ گهڻو ڪري ساڳيو آهي ۽ منهنجو خيال آهي ته اهو ڪافي ناقابل يقين آهي ته 20 سالن ۾ تمام گهٽ تبديل ٿي چڪو آهي! اهو ڪوڊ آئي هاسٽل نوٽ بڪ جي طور تي دستياب آهي جيڪڏهن توهان پنهنجي پاڻ سان تجربا ڪرڻ چاهيندا.
ايڊٽ ڪيو: مون صرف ڳولي لڌو آهي ’مانوائيڊس کان وٺي ويجهي سيمرنگس تائين: جوهر MonadPlus
۽ Alternative
‘ جيڪو اهو ظاهر ڪري ٿو ته منهنجي Maybe
بنياد تي ڀاڙيندڙ Alternative
قانونن تي سختي سان عمل نٿو ڪري . ذهن ۾ رکڻ لاءِ ڪا شي جيڪڏهن توهان ان کي استعمال ڪرڻ جو منصوبو ٺاهيو يا ان بابت ڪجهه هو!
آلن او ڊونيل ، اينڊري موخوف ، ايني چرڪيف ، جوليا ايوانز ، ۽ نوح لک ايسسٽرل مهرباني ڪري رايا ۽ راءِ ڏيڻ جي مهرباني .