调试 Haskell 应用程序
debugging a Haskell application
学习了一些基础知识后,我想在 Haskell 中尝试 "real world application",从 Bittorrent 客户端开始。按照 blog post, I did NOT use the Attoparsec parser combinator library. Instead following through 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 blog, the library, and the book的源代码read/understood的混合代码。 download
函数只打印从解析器获得的 "parse tree",一旦我让解析器工作,就会填充 download
函数并对其进行测试。
- 解析器无法处理少数 torrent 文件。 :( 肯定有可能我错误地使用了参考资料中的代码。并且想知道是否有明显的问题。
- 它适用于 "toy" 个示例以及从 combinatorrent
中选取的测试文件
- 当我选择像 Debian/Ubuntu 等真实世界的 torrent 时,这失败了。
- 我想调试看看发生了什么,使用 GHCI 进行调试似乎并不直接,我已经尝试过
:trace
/ :history
中提到的样式调试 document , 但看起来很原始 :-) .
- 我向专家提出的问题是:"how to debug!!" :-)
- 非常感谢有关如何调试此问题的任何提示。
谢谢。
因为 Haskell 代码是纯粹的,所以 "stepping" 通过它不像其他语言那么重要。当我单步执行一些 Java 代码时,我经常试图查看某个变量在哪里发生了变化。鉴于事物是不可变的,这在 Haskell 中显然不是问题。
这意味着我们还可以 运行 GHCi 中的代码片段来调试正在发生的事情,而不用担心我们 运行 会改变某些全局状态,或者我们 运行 的工作方式与在我们的程序深处调用时的工作方式完全不同。这种工作模式受益于迭代您的设计,慢慢构建它以处理所有预期输入。
解析总是有点不愉快 - 即使是命令式语言也是如此。没有人想要 运行 解析器只是为了取回 Nothing
- 你想知道 为什么 你什么也没取回。为此,大多数解析器库都有助于为您提供一些有关问题所在的信息。这是默认情况下使用像 attoparsec
. Also, attoparsec
works with ByteString
这样的解析器的要点——非常适合二进制数据。如果您想推出自己的解析器实现,您也必须对其进行调试。
最后,根据您的评论,您似乎遇到了字符编码问题。这正是我们 ByteString
- it represents a packed sequence of bytes - no encodings. The extension OverloadedStrings
甚至可以很容易地使 ByteString
看起来像常规字符串的文字的原因。
学习了一些基础知识后,我想在 Haskell 中尝试 "real world application",从 Bittorrent 客户端开始。按照 blog post, I did NOT use the Attoparsec parser combinator library. Instead following through 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 blog, the library, and the book的源代码read/understood的混合代码。 download
函数只打印从解析器获得的 "parse tree",一旦我让解析器工作,就会填充 download
函数并对其进行测试。
- 解析器无法处理少数 torrent 文件。 :( 肯定有可能我错误地使用了参考资料中的代码。并且想知道是否有明显的问题。
- 它适用于 "toy" 个示例以及从 combinatorrent 中选取的测试文件
- 当我选择像 Debian/Ubuntu 等真实世界的 torrent 时,这失败了。
- 我想调试看看发生了什么,使用 GHCI 进行调试似乎并不直接,我已经尝试过
:trace
/:history
中提到的样式调试 document , 但看起来很原始 :-) . - 我向专家提出的问题是:"how to debug!!" :-)
- 非常感谢有关如何调试此问题的任何提示。
谢谢。
因为 Haskell 代码是纯粹的,所以 "stepping" 通过它不像其他语言那么重要。当我单步执行一些 Java 代码时,我经常试图查看某个变量在哪里发生了变化。鉴于事物是不可变的,这在 Haskell 中显然不是问题。
这意味着我们还可以 运行 GHCi 中的代码片段来调试正在发生的事情,而不用担心我们 运行 会改变某些全局状态,或者我们 运行 的工作方式与在我们的程序深处调用时的工作方式完全不同。这种工作模式受益于迭代您的设计,慢慢构建它以处理所有预期输入。
解析总是有点不愉快 - 即使是命令式语言也是如此。没有人想要 运行 解析器只是为了取回 Nothing
- 你想知道 为什么 你什么也没取回。为此,大多数解析器库都有助于为您提供一些有关问题所在的信息。这是默认情况下使用像 attoparsec
. Also, attoparsec
works with ByteString
这样的解析器的要点——非常适合二进制数据。如果您想推出自己的解析器实现,您也必须对其进行调试。
最后,根据您的评论,您似乎遇到了字符编码问题。这正是我们 ByteString
- it represents a packed sequence of bytes - no encodings. The extension OverloadedStrings
甚至可以很容易地使 ByteString
看起来像常规字符串的文字的原因。