我是否可以获得实现概念的帮助,“当字符串发生变化时,其类型会发生变化”

时间:2011-09-19 18:40:51

标签: haskell types

在#haskell的某一天,有人提到了字符串更改时字符串类型应该如何变化的概念。这让我想起了我的项目中的一些代码。它一直困扰着我,我无法说清楚原因。我现在推测,原因是因为我没有实施这个概念。这是下面的代码,接下来是一些关于如何开始改进它的想法。我想要的是“你走在正确的轨道上”的效果。或者,“不,走开。”或“这是你应该注意的另一件事。”。

> processHTML :: String -> [[String]]
> processHTML htmlFILE =
>            let parsedHTML        = parseTags htmlFILE
>                allTagOpens       = sections (~== TagOpen "a" [("href","")]) parsedHTML
>                taggedTEXT        = head $ map (filter isTagOpen) allTagOpens
>                allHREFS          = map (fromAttrib "href") taggedTEXT
>                allPotentials     = map (dropWhile (/= '?')) allHREFS
>                removedNulls      = filter (not . null) allPotentials
>                removedQs         = map (drop 1) removedNulls
>            in map (splitOn "&") removedQs

这里的想法是我正在使用原始HTML并过滤掉我不想要的所有内容,直到我得到我想要的内容。每个let绑定代表过滤的一个阶段。这可能是数据结构的基础,如下所示:

> data Stage = Stage1 Foo
>            | Stage2 Bar
>            | Stage3 Baz

Foo Bar和Baz是适当的数据类型;例如,String或TagOpen,具体取决于我在过滤过程中的阶段。当我添加错误处理代码时,我可以使用此数据类型来获取准确的信息。另外,它可以帮助我跟踪发生的事情。

反馈意见。

2 个答案:

答案 0 :(得分:3)

This page讨论了如何使用类型来强制执行操作安全性,并导致在编译时出现常见错误。我不确定,但我认为这与您尝试实施的内容类似。

问题的一个例子:
您正在运行需要使用数据库的Web应用程序。它根据用户名和密码生成SQL查询(例如),并将其发送到数据库服务器,获取响应并将其呈现给用户。这很有用。但是,一个非常粗鲁的用户在" OR 1 = 1; --中输入用户名。你能想象将该字符串发送到以下查询:

SELECT * FROM users WHERE password = "$" AND username = "$";

灾害!

基本解决方案:
1)为可安全发送到数据库服务器的字符串创建一个类型(即GoodSQLString)
2)确保所有GoodSQLString确实是安全的(也许构造函数通过转义函数传递参数查询字符串)
3)只允许将GoodSQLString从应用程序发送到数据库服务器

尽管如此,很难说这会如何转化为您的processHTML问题。也许类型签名应该是processHTML :: HTML -> [Tags] - 除非传递无效HTML的String是有意义的。

答案 1 :(得分:3)

你走在正确的轨道上。

首先,当您构建这样的长管道时,您可能更喜欢直接编写函数:

> processHTML :: String -> [[String]]
> processHTML =
>            parseTags
>            >>> sections (~== TagOpen "a" [("href","")])
>            >>> head $ map (filter isTagOpen)
>            >>> map (fromAttrib "href")
>            >>> map (dropWhile (/= '?'))
>            >>> filter (not . null)
>            >>> map (drop 1)
>            >>> map (splitOn "&")

这使用Control.Category.(>>>),这只是(至少在这种情况下)翻转函数组合。

现在,对于您的实际问题,您似乎正在使用tagsoup包来解析标记。这已经在整个管道中进行了一些类型更改:parseTags生成Tag,一些函数对其进行操作,然后fromAttrib返回到字符串。

根据您将要做多少工作,我可能会创建一个新类型:

newtype QueryElement = QE { unQE :: String } deriving (Eq, Show)

> processHTML :: String -> [[QueryElement]]
> processHTML =
>            parseTags
>            >>> sections (~== TagOpen "a" [("href","")])
>            >>> head $ map (filter isTagOpen)
>            >>> map (fromAttrib "href")
>            >>> map (dropWhile (/= '?'))
>            >>> filter (not . null)
>            >>> map (drop 1)
>            >>> map (splitOn "&" >>> map QE)

此处仅更改了最后一行,将QE newtype标记添加到每个元素。

根据您的使用情况,您可以采取不同的方法。例如,您可能希望向URI添加更多信息,而不是仅仅收集查询变量。或者,您可能希望折叠查询项并直接生成Map String String

最后,如果您尝试获得类型安全性,通常不会使用Stage这样的总和类型。这是因为每个构造函数都创建了相同类型的值,因此编译器无法进行任何额外的检查。相反,你要为每个阶段创建一个单独的类型:

data Stage1 = Stage1 Foo
data Stage2 = Stage2 Bar
data Stage3 = Stage3 Baz

doStage1 :: Stage1 -> Stage2

doStage2 :: Stage2 -> Stage3

创建非常精细的类和数据结构很容易,但在某些时候它们会失控。例如,在您的函数allPotentialsremovedNullsremovedQs中,您可能只想处理字符串。没有很多语义可以附加到这些阶段的输出上,特别是因为它们是稍微大一点的过程中的部分步骤。