在#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,具体取决于我在过滤过程中的阶段。当我添加错误处理代码时,我可以使用此数据类型来获取准确的信息。另外,它可以帮助我跟踪发生的事情。
反馈意见。
答案 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
创建非常精细的类和数据结构很容易,但在某些时候它们会失控。例如,在您的函数allPotentials
,removedNulls
和removedQs
中,您可能只想处理字符串。没有很多语义可以附加到这些阶段的输出上,特别是因为它们是稍微大一点的过程中的部分步骤。