如何将 IO [[String]] 扁平化?

5

我刚接触Haskell,有些概念不是很理解。

当我在使用IO时,我想要将 IO [[String]] 压成一维数组。

这是我尝试的代码示例:

module DatabaseTestSO where

import Database.HDBC
import Database.HDBC.MySQL
import Data.Foldable

convSqlValue :: [SqlValue] -> [String]
convSqlValue xs = [ getString x | x <- xs ]
    where getString value = case fromSql value of
                Just x -> x
                Nothing -> "Null"

listValues :: [[SqlValue]] -> [[String]]
listValues [] = []
listValues xs = [ convSqlValue x | x <- xs ]

flatten :: [[a]] -> [a]
flatten = Data.Foldable.foldl (++) []

domains :: IO [[String]]
domains =
    do  conn <- connectMySQL defaultMySQLConnectInfo {
                mysqlHost       = "hostname",
                mysqlDatabase   = "dbname",
                mysqlUser       = "username",
                mysqlPassword   = "pass" }

        queryDomains <- quickQuery conn "SELECT name FROM domains" []

        return (listValues queryDomains)

在GHCi中,使用[[String]]可以按预期工作:

*DatabaseTestSO> flatten [["blah","blab","wah"],["bloh","blob","woh"],["blih","blib","wuh"]]
["blah","blab","wah","bloh","blob","woh","blih","blib","wuh"]

但不适用于 IO [[String]],我该怎么办?

*DatabaseTestSO> flatten domains 

<interactive>:1:9:
    Couldn't match expected type `[[a0]]'
                with actual type `IO [[String]]'
    In the first argument of `flatten', namely `domains'
    In the expression: flatten domains
    In an equation for `it': it = flatten domains

我猜我不能使用一个本应该是纯函数的IO类型?我能把IO [[String]] 转换成 [[String]] 吗?如何正确地解决这个问题?


4
flatten 被称为 concat,并且定义在 Prelude 中。 - Thomas Eding
我明白了。当不使用GHCi时,Prelude会自动导入吗? - matthias krull
2个回答

15

你必须意识到IO something的含义。它不是一个something,而是一个会返回something的操作(在这种情况下,something[[String]])。因此,在执行返回该值的操作之前,您无法对其执行任何操作。

解决问题有两个选项:

  1. 执行操作并使用结果,可以通过以下方式完成:

    do
      ds <- domains       -- Perform action, and save result in ds
      return $ flatten ds -- Flatten the result ds
    
  2. 创建一个新的操作,该操作获取某个操作的结果,并对其应用函数。然后新的操作返回转换后的值。使用Control.Monad模块中的liftM函数实现。

  3. import Control.Monad
    -- ...
    
    do
      -- Creates a new action that flattens the result of domains
      let getFlattenedDomains = liftM flatten domains
    
      -- Perform the new action to get ds, which contains the flattened result
      ds <- getFlattenedDomains
    
      return ds
    

PS. 你可能需要将domains变量重命名为getDomains以明确其作用。它不是一个纯值,而是返回纯值的单子操作。


你的解释非常有帮助。 - matthias krull
3
注意:fmap = liftM = liftA = <$> - Dan Burton

10

你无法从IO中获取任何东西,因此你需要将flatten提升到其中运行。最简单的方法是使用fmap——就像map在列表上应用函数一样,fmap在任何Functor实例上应用函数,比如IO

flattenIO xs = fmap flatten xs

在更一般的情况下,您可以使用 do 表示法来访问 IO 计算中的内容。例如:

flattenIO xs = do ys <- xs
                  return (flatten ys)

在这种情况下,这只是一种拐弯抹角的方式来编写fmap


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接