调试Haskell应用程序

时间:2016-11-29 02:54:29

标签: debugging haskell bittorrent

在学习了一些基础知识后,我想在Haskell中尝试一个“真实世界的应用程序”,从Bittorrent客户端开始。按照此blog post的解释,我没有使用Attoparsec parser combinator库。而是通过Huttons book,我开始编写Parser Combinators。这是我到目前为止的代码(仍处于解析阶段,未来很长的路程):

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
    []       -> []
    v@(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 librarythe book的源代码中读取/理解的代码组合。 download函数只打印从解析器获得的“解析树”,一旦我得到解析器工作,将填写download函数并测试它。

  1. 解析器不能处理少数torrent文件。 :(我肯定有可能错误地使用了引用中的代码。并且想知道是否有任何明显的内容。
  2. 它适用于“玩具”示例以及从combinatorrent
  3. 中挑选的测试文件
  4. 当我选择像Debian / Ubuntu等真实世界的洪流时,这会失败。
  5. 我想调试并看看发生了什么,使用GHCI进行调试似乎并不简单,我已经尝试了document中提到的:trace / :history样式调试,但是看起来很原始:-)。
  6. 我向专家提出的问题是:“如何调试!!” :-)
  7. 非常感谢有关如何调试此问题的任何提示。
  8. 感谢。

1 个答案:

答案 0 :(得分:3)

因为Haskell代码是纯粹的,&#34;踩踏&#34;通过它比其他语言不那么重要。当我单步执行一些Java代码时,我经常试图查看某个变量在哪里被更改。这显然是Haskell中的一个问题,因为事情是不可改变的。

这意味着我们也可以在GHCi中运行代码片段来调试正在发生的事情,而不用担心我们运行的东西会改变某些全局状态,或者我们运行的东西会有什么不同,如果深入调查我们程序。这种工作模式可以从迭代设计中慢慢构建,以便在所有预期输入上工作。

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

最后,根据您的评论,您似乎遇到了字符编码问题。这正是我们拥有ByteString的原因 - 它代表了一个打包的字节序列 - 没有编码。扩展程序OverloadedStrings甚至可以很容易地使ByteString文字看起来像常规字符串。