Вопрос лени Haskell или почему эта монада не работает, как ожидалось - PullRequest
4 голосов
/ 09 января 2011

это своего рода огромный кусок кода, он наконец стал огромным из-за монадических вещей, но задача проста: проанализировать следующую строку в структуре данных:

"hello (some, args)" -> [("fid", "hello"), ("sym", "("), ("args", "some, args"), ( "сИМ", ")")]

но код, который я написал, выдает следующее:

"hello (some, args)" -> [("fid", ""), ("sym", "("), ("args", ""), ("sym", ")")]

Как вы можете видеть, значения 'args' и 'fid' теряются где-то по пути, я подозреваю, что компилятор решил не вычислять их по какой-то таинственной причине.

Полагаю, код совершенно плохой, также я пометил части "?", Которые кажутся мне бесполезными, но компилятор заставил меня оставить их на месте:)

А вот и код:

type PStream = String
type PToken a = (String, a)
data Pstate a = Pstate (String -> ([PToken String], PStream)) a

instance Monad Pstate where
    return x = Pstate (\_ -> ([("start", "")], "?")) x -- not used ?
    (Pstate bindparser v) >>= f  = Pstate newparser fv
        where
            Pstate fparser fv = f v
            (btok, brest) = bindparser "this string also not used"
            (tok, rest) = fparser brest
            newparser _ = (btok ++ tok, rest)

-- parsers
parseFid :: Pstate String
parseFid = Pstate parser "???"
    where parser r = let (fid, rest) = span (/= '(') r in ([("fid", fid)],rest)

parseSym :: Char -> Pstate String
parseSym c = Pstate parser "???"
    where parser r = let rest = parseOne c r in ([("sym", [c])],rest)

parseOne s (h:t) = if h == s then t else error $ "symbol not match:" ++ [h] ++ " /= " ++ [s]
parseOne s []    = []

parseArgs :: Pstate String
parseArgs = Pstate parser "???"
    where parser r = let (args,rest) = span (/=')') r in ([("args", args)],rest)

-- util
load :: String -> Pstate String
load s = Pstate (\ls -> ([("load", "")],ls)) "???"

runP :: Pstate String -> ([PToken String], PStream)
runP (Pstate fparser fv) = fparser "???"

-- combined parser
parseFunction :: String -> Pstate String
parseFunction s = do
    load s --- should be 'return' here ?
    parseFid
    parseSym '('
    parseArgs
    parseSym ')'

main = putStrLn $ show $ runP $ parseFunction "hello(a b c)"

Ответы [ 2 ]

3 голосов
/ 09 января 2011

Во-первых, о "???", который вам пришлось оставить там.Рассмотрим ваше определение Pstate:

data Pstate a = Pstate (String -> ([PToken String], PStream)) a

Это означает, что ваш конструктор данных имеет следующий тип:

Pstate :: (String -> ([PToken String], PStream)) -> a -> Pstate a

Это конструкция по умолчанию для монады.Если вы определяете монадные комбинаторы, то на самом деле нередко бывает, что некоторые комбинаторы там, где это не нужно, поэтому в данном случае принято оставить значение ().

Но на самом деле я думаю, что ваш код оченьстранно, кажется, что вы не поняли смысл монады с состоянием.Позвольте мне объяснить:

Обычно вычисление с состоянием имеет такой тип:

data MyState a = MyState (TypeOfState -> (a, TypeOfState))

Это означает, что ваше монадическое действие на самом деле является своего рода вычислением, которое делает что-то (возможно с вашей частьюсостояния), а затем возвращает результат и новое состояние.Состояние заключено в монаде, поэтому вам не нужно об этом думать.

В вашем коде вы используете тот же шаблон, но несколько другой.Похоже, что вы исправили результат вычисления на [PToken String].Позвольте мне немного исправить ваше определение:

data Pstate a = Pstate (PStream -> (a, PStream))

Итак, теперь вы получаете возвращаемое значение вашего вычисления, применяя комбинаторы, которые выглядят так:

instance Monad Pstate where
  -- return should just wrap the computation up, so no changes
  return x = Pstate (\p -> (x,p))
  parser1 >>= parser2  = Pstate $ \input -> let
    Pstate parser1' = parser1
    Pstate parser2' = parser2
    (output, rest) = parser1' input
    result = parser2' output rest
    in result

Теперь,вы можете посмотреть на сигнатуры типов для ваших парсеров, они должны выглядеть примерно так: parseFid :: Pstate [PToken PStream].Это означает, что ваш синтаксический анализатор потребляет некоторый ввод и возвращает проанализированный материал как [PToken PStream] и устанавливает новый вход в то, что осталось.Рассмотрим определение parseFid о том, как оно может выглядеть:

parseFid :: Pstate [PToken PStream]
parseFid = Pstate $ \r -> let (fid, rest) = span (/= '(') r in ([("fid", fid)],rest)

Остальное оставлено в качестве упражнения для читателя.Я бы посоветовал вам переформулировать ваш парсер, используя вместо этого монаду State из Control.Monad.State.Strict.Вы увидите, что приведенная выше монада в основном та же самая.


На самом деле, в большинстве случаев проще полагаться на существующие и хорошо известные инструменты вместо того, чтобы сворачивать собственный анализатор.Вот синтаксический анализатор того, что вам нужно создать с помощью Parsec, современной библиотеки для анализа:

import Text.Parsec

parseFunction = do name   <- parseName
                   obrace <- parseOpeningBrace
                   args   <- parseArguments
                   cbrace <- parseClosingBrace
                   return [name,obrace,args,cbrace]

parseName         = many (noneOf "(") >>= \name -> return ("fid",name)
parseOpeningBrace = char '(' >> return ("sym","(")
parseArguments    = many (noneOf ")") >>= \name -> return ("args",name)
parseClosingBrace = char ')' >> return ("sym",")")

main = case parse parseFunction "" "hello(some, args)" of
  Left error   -> print error
  Right result -> print result

Вот вывод:

[("fid","hello"),("sym","("),("args","some, args"),("sym",")")]

Я бы действительно предложил вамПодумайте о лучшем представлении анализируемой функции, это может упростить задачу.

2 голосов
/ 09 января 2011

Если вы запустите код как опубликованный, вы увидите, что строка "this string also not used" фактически используется, так как вы получите этот вывод:

([("load",""),("fid","this string also not used"),("sym","("),("args","this string also not used"),("sym",")")],"")

Фактически строка в основном используется в качестве входных данных для всех анализаторов. В определении >>= строка указывается в качестве входных данных для bindparser. Затем этот синтаксический анализатор принимает его в качестве входных данных и создает из него токены. parseFid например производит токен ("fid","this string also not used").

newparser, созданный в >>=, игнорирует любые входные данные, которые он может получить позже, он просто возвращает результат анализа "this string also not used". Аналогично, парсер, созданный с помощью return, игнорирует значение, которое он должен возвращать.

Парсеры, созданные с помощью bind, не должны игнорировать / переопределять свои входные данные для правильной работы синтаксического анализа.

Также вам следует решить, какую роль должен выполнять второй параметр Pstate, поскольку в данный момент он в основном содержит «???», что не выглядит особенно полезным.

...