【问题标题】:Could I get help implementing a concept, "When a String changes, its type changes"我可以帮助实现一个概念,“当一个字符串改变时,它的类型改变”
【发布时间】:2011-09-19 18:40:51
【问题描述】:

有一天在#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 是合适的数据类型;例如,一个字符串或 TagOpen,具体取决于我在过滤过程中所处的阶段。当我添加错误处理代码时,我可以使用这种数据类型来获取精确的信息。另外,它可以帮助我跟踪发生的事情。

感谢您的反馈。

【问题讨论】:

  • 你为什么要努力改变类型?另外,你能描述一下processHTML应该做什么吗?
  • 嗯,我想我解释了为什么我想改变类型。我认为这将帮助我在错误处理期间获得更精确的信息(尚未实现)。另外,虽然我是写这篇文章的人,但我必须在 ghci 中运行这些 let 绑定以查看发生了什么。我想我可以使用类型向自己传达更多信息。
  • ProcessHTML 正在从通过屏幕抓取生成的 HTML 中捕获我需要的数据,方法是删除我不需要的所有内容,直到我只剩下我想要的东西。

标签: haskell types


【解决方案1】:

你在正确的轨道上。

首先,当您像这样构建长管道时,您可能更喜欢直接组合函数:

> 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 这样的 sum 类型。这是因为每个构造函数都会创建一个相同类型的值,所以编译器不能做任何额外的检查。相反,您将为每个阶段创建一个单独的类型:

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

doStage1 :: Stage1 -> Stage2

doStage2 :: Stage2 -> Stage3

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

【讨论】:

    【解决方案2】:

    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的字符串是有意义的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-08
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多