Ну, вы пытаетесь разобрать дерево байтов из битового потока. Parsing - один из тех случаев, когда он платит, чтобы создать некоторую структуру: мы собираемся написать библиотеку миниатюрных парсерных комбинаторов в стиле How to Replace Failure by a List of Successes, что позволит нам написать наш код в идиоматическом функциональном стиле и делегировать много работайте на машине.
Перевод the old rhyme на язык монадных трансформаторов, и чтение «строка», как «бит-строкой», мы имеем
newtype Parser a = Parser (StateT [Bool] [] a)
deriving (Functor, Applicative, Monad, Alternative)
runParser :: Parser a -> [Bool] -> [(a, [Bool])]
runParser (Parser m) = runStateT m
Анализатор представляет собой монадическая вычисления, который работает statefully на потоке Booleans, получая набор успешно проанализированных a
с. GHC GeneralizedNewtypeDeriving
сверхдержавы позволяют мне отойти от шаблонных экземпляров Monad
и др.
Цель состоит в том, чтобы написать Parser (Tree SevenBits)
- синтаксический анализатор, который возвращает дерево перегородок булевых. (Вы можете превратить 7 бит в Word8
в свой досуг на deriving a Functor
instance за Tree
и используя fmap
.) Я собираюсь использовать следующее определение Tree
, потому что оно проще - я уверен, что вы можете понять, как адаптировать этот код в ваших целях.
data Tree a = Leaf a | Node (Tree a) (Tree a) deriving Show
type SevenBits = (Bool, Bool, Bool, Bool, Bool, Bool, Bool)
Вот парсер, который пытается потреблять один бит из входного потока, в противном случае, если он пуст:
one :: Parser Bool
one = Parser $ do
stream <- get
case stream of
[] -> empty
(x:xs) -> put xs *> return x
Вот один, который пытается потреблять конкретных биты из входного потока, в противном случае, если он не соответствует:
bit :: Bool -> Parser()
bit b = do
i <- one
guard (i == b)
Здесь я потянув последовательность из семи Booleans из входного потока с использованием replicateM
и упаковывая их в кортеж. Мы будем использовать это для заполнения содержимого узлов Leaf
.
sevenBits :: Parser SevenBits
sevenBits = pack7 <$> replicateM 7 one
where pack7 [a,b,c,d,e,f,g] = (a, b, c, d, e, f, g)
Теперь мы можем, наконец, написать код, который анализирует структуру дерева. Мы будем выбирать между вариантами Node
и Leaf
, используя <|>
.
tree :: Parser (Tree SevenBits)
tree = node <|> leaf
where node = bit False *> liftA2 Node tree tree
leaf = bit True *> fmap Leaf sevenBits
Если node
удается разбор младшего бита из головы потока, она продолжает рекурсивно разобрать кодирование левого поддерева с последующим правом поддерева, секвенирование аппликативных действий с liftA2
. Хитрость заключается в том, что node
терпит неудачу, если он не встречает низкий бит в начале входного потока, который сообщает <|>
, чтобы отказаться от node
и вместо этого попробуйте leaf
.
Обратите внимание, что структура tree
отражает структуру самого типа Tree
. Это аппликативный синтаксический анализ на работе. Мы могли бы поочередно структурировать этот синтаксический анализатор монадически, сначала используя one
для разбора произвольного бита, а затем используя анализ case
на бит, чтобы определить, следует ли продолжать разбирать пару деревьев или лист. По-моему, эта версия проще, более декларативной и менее многословной.
Также сравните этот код с низкоуровневым стилем решения foldr
@ behzad.nouri. Вместо того, чтобы создавать явную машину конечного состояния, которая переключается между узлами анализа и листьями - идея с императивным вкусом - моя конструкция позволяет вам декларативно описать грамматику на машине с помощью стандартных функций, таких как liftA2
и <|>
, и полагайте, что абстракции будут выполнять правильная вещь.
В любом случае, здесь я разбираю простое дерево, состоящее из пары Leaf
s, содержащей (двоично-кодированные) номера 0
и 1
. Как вы можете видеть, он возвращает единственный успешный синтаксический разбор и пустой поток оставшихся бит.
ghci> runParser tree $ map (>0) [0, 1, 0,0,0,0,0,0,0, 1, 0,0,0,0,0,0,1]
[(Node (Leaf (False, False, False, False, False, False, False)) (Leaf (False, False, False, False, False, False, True)),[])]
Это именно то, что я искал! Это элегантное и сжатое решение. Я подумывал о том, чтобы как-то вычислить левую и правую ветви рекурсивно, но не мог понять, как подавать соответствующий список в призыв к правильному. Возвращение списка в паре - умная идея! – David
Для чего это стоит, это, по сути, выписанная версия монады «Государство». –
Да, это на самом деле монада «Государство». Точнее, это 'Parser'monad, который работает над' [Int] '. – Euge