2016-11-29 143 views
4

在学习了一些基础知识之后,我想尝试一下Haskell中的“真实世界应用程序”,它以Bittorrent客户端开始。通过这个blog post的解释,我没有使用Attoparsec parser combinator库。相反,通过Huttons book,我开始编写解析器组合器。这是代码,我到目前为止(仍然在解析阶段,提前远行):调试Haskell应用程序

module Main where 

import System.Environment (getArgs) 
import qualified Data.Map as Map 
import Control.Monad (liftM, ap) 
import Data.Char (isDigit, isAlpha, isAlphaNum, ord) 
import Data.List(foldl') 

main :: IO() 
main = do 
    [fileName] <- getArgs 
    contents <- readFile fileName 
    download . parse $ contents 

parse :: String -> Maybe BenValue 
parse s = case runParser value s of 
    []  -> Nothing 
    [(p, _)] -> Just p 

download :: Maybe BenValue -> IO() 
download (Just p) = print p 
download _  = print "Oh!! Man!!" 

data BenValue = BenString String 
    | BenNumber Integer 
    | BenList [BenValue] 
    | BenDict (Map.Map String BenValue) 
    deriving(Show, Eq) 

-- From Hutton, this follows: a Parser is a function 
-- that takes a string and returns a list of results 
-- each containing a pair : a result of type a and 
-- an output string. (the string is the unconsumed part of the input). 
newtype Parser a = Parser (String -> [(a, String)]) 

-- Unit takes a value and returns a Parser (a function) 
unit :: a -> Parser a 
unit v = Parser (\inp -> [(v, inp)]) 

failure :: Parser a 
failure = Parser (\inp -> []) 

one :: Parser Char 
one = Parser $ \inp -> case inp of 
     []  -> [] 
     (x: xs) -> [(x, xs)] 

runParser :: Parser a -> String -> [(a, String)] 
runParser (Parser p) inp = p inp 

bind :: Parser a -> (a -> Parser b) -> Parser b 
bind (Parser p) f = Parser $ \inp -> case p inp of 
    []   -> [] 
    [(v, out)] -> runParser (f v) out 

instance Monad Parser where 
    return = unit 
    p >>= f = bind p f 

instance Applicative Parser where 
    pure = unit 
    (<*>) = ap 

instance Functor Parser where 
    fmap = liftM 

choice :: Parser a -> Parser a -> Parser a 
choice p q = Parser $ \inp -> case runParser p inp of 
    [] -> runParser q inp 
    x -> x 

satisfies :: (Char -> Bool) -> Parser Char 
satisfies p = do 
    x <- one 
    if p x 
    then unit x 
    else failure 

digit :: Parser Char 
digit = satisfies isDigit 

letter :: Parser Char 
letter = satisfies isAlpha 

alphanum :: Parser Char 
alphanum = satisfies isAlphaNum 

char :: Char -> Parser Char 
char x = satisfies (== x) 

many :: Parser a -> Parser [a] 
many p = choice (many1 p) (unit []) 

many1 :: Parser a -> Parser [a] 
many1 p = do 
    v <- p 
    vs <- many p 
    unit (v:vs) 

peek :: Parser Char 
peek = Parser $ \inp -> case inp of 
    []  -> [] 
    [email protected](x:xs) -> [(x, v)]  

taken :: Int -> Parser [Char] 
taken n = do 
    if n > 0 
    then do 
     v <- one 
     vs <- taken (n-1) 
     unit (v:vs) 
    else unit [] 

takeWhile1 :: (Char -> Bool) -> Parser [Char] 
takeWhile1 pred = do 
    v <- peek 
    if pred v 
    then do 
     one 
     vs <- takeWhile1 pred 
     unit (v:vs) 
    else unit [] 

decimal :: Integral a => Parser a 
decimal = foldl' step 0 `fmap` takeWhile1 isDigit 
    where step a c = a * 10 + fromIntegral (ord c - 48) 

string :: Parser BenValue 
string = do 
    n <- decimal 
    char ':' 
    BenString <$> taken n 

signed :: Num a => Parser a -> Parser a 
signed p = (negate <$> (char '-' *> p)) 
    `choice` (char '+' *> p) 
    `choice` p 

number :: Parser BenValue 
number = BenNumber <$> (char 'i' *> (signed decimal) <* char 'e') 

list :: Parser BenValue 
list = BenList <$> (char 'l' *> (many value) <* char 'e') 

dict :: Parser BenValue 
dict = do 
    char 'd' 
    pair <- many ((,) <$> string <*> value) 
    char 'e' 
    let pair' = (\(BenString s, v) -> (s,v)) <$> pair 
    let map' = Map.fromList pair' 
    unit $ BenDict map' 

value = string `choice` number `choice` list `choice` dict 

以上是/代码读取的混合来自三个来源的源代码理解the blogthe library ,和the bookdownload函数只是打印从解析器获得的“解析树”,一旦我得到解析器的工作将填写download函数并进行测试。

  1. 解析器不能处理少数的torrent文件。 :(肯定是有机会,我可能已经从参考使用不正确的代码,而且想知道是否有什么明显的。
  2. 它适用于“玩具”的例子,也对测试文件从combinatorrent
  3. 回升当我选择像Debian/Ubuntu等真正的世界洪流,这失败了。
  4. 我想调试,看看发生了什么,与GHCI调试似乎并不直接,我试过:trace/:history风格调试提到在这document,但看起来很原始:-)。
  5. 我向专家提出的问题是:“如何调试!!” :-)
  6. 真的很感激任何关于如何调试的提示。

谢谢。

+0

我想这个问题稍微偏离了主题,但是鉴于你正在使用二进制数据,使用'readFile'和'String'可能不是最好的主意。我会坚持['ByteString'](https://hackage.haskell.org/package/bytestring-0.10.8.1/docs/Data-ByteString.html),而不是所有相应的功能(它有一个版本['readFile'](http://hackage.haskell.org/package/bytestring-0.10.8.1/docs/Data-ByteString-Char8.html#v:readFile))。 – Alec

+0

此外,用第2点中指定的文件试验你的代码会给我'hGetContents:无效的参数(无效的字节序列)'(这可能与我的第一条评论有关)。你看到了什么输出? – Alec

+0

我曾试过这个Windows机器。如果这很重要,不要起诉,让我试试这个不同的操作系统,然后回来。谢谢。 – user3169543

回答

3

因为Haskell代码是纯粹的,所以“步进”并不像其他语言那样重要。当我浏览一些Java代码时,我经常试图查看某个变量被更改的位置。在Haskell中,这显然是一个非问题,因为事物是不可变的。

这意味着我们也可以在GHCi中运行代码片段来调试正在发生的事情,而不用担心我们运行的是要改变某个全局状态,或者我们运行的任何方式都不同于如果调用深入内部我们的计划。这种工作模式从迭代设计开始慢慢构建它,以便处理所有预期的输入。

解析总是有点不愉快 - 即使在命令式语言中。没有人希望运行解析器只是为了取回Nothing - 你想知道为什么你没有收回任何东西。为此,大多数解析器库有助于向您提供有关出错的信息。这是使用像attoparsec这样的解析器的一点。另外,attoparsec默认与ByteString一起使用 - 适用于二进制数据。如果你想推出你自己的解析器实现,你也必须调试它。

最后,根据您的意见,它看起来像你有字符编码的问题。这正是我们为什么有ByteString的原因 - 它表示一个打包的字节序列 - 无编码。扩展OverloadedStrings甚至可以使ByteString文字看起来就像普通字符串一样简单。