Haskell 函数中的非详尽模式匹配

non-exhaustive pattern matches in Haskell function

这是我的函数。

toResult :: [SqlValue] -> IO Course
toResult [ fromSql -> courseid,   fromSql -> title,
       fromSql -> name,  fromSql -> version,
       fromSql -> cdate, fromSql -> fid ] = do
    let courseXML = show (fid :: Int)
    xml <- B.readFile courseXML
    let Right doc = parseXML courseXML xml
    let passing = read $ T.unpack $ fromJust
            $ lookup (T.pack "PassingScore")
            $ elementAttrs $ head $ docContent doc
    return (Course courseid title name version cdate passing)

任何调用此函数但未传递恰好包含六个 SqlValue 值的列表的参数都是错误的。

编译此 returns 非详尽模式匹配警告:

Pattern match(es) are non-exhaustive
In an equation for `toResult':
    Patterns not matched:
        []
        [_]
        [_, _]
        [_, _, _]
        ...

我知道我可以忽略警告,但我正在努力学习如何正确地执行此 Haskell 操作。

感谢任何和所有建议。

戴夫

更新...

根据此处提出的建议,我已将我的代码更改为现在的形式。

toResult :: (SqlValue, SqlValue, SqlValue, SqlValue, SqlValue, SqlValue) -> IO Course
toResult ( fromSql -> courseid,   fromSql -> title,
           fromSql -> name,  fromSql -> version,
           fromSql -> cdate, fromSql -> fid ) = do
    let courseXML = show (fid :: Int)
    xml <- B.readFile courseXML
    let Right doc = parseXML courseXML xml
    let passing = read $ T.unpack $ fromJust
                $ lookup (T.pack "PassingScore")
                $ elementAttrs $ head $ docContent doc
    return (Course courseid title name version cdate passing)

listToTuple ::  [SqlValue] -> (SqlValue, SqlValue, SqlValue, SqlValue, SqlValue, SqlValue)
listToTuple [a,b,c,d,e,f] = (a,b,c,d,e,f)
listToTuple xs            = error "Wrong number of values"

getCourses :: Connection -> IO [Course]
getCourses conn = do
    let query = "{Actual query deleted to protect the innocent.}"
    res <- quickQuery conn query []
    mapM toResult (listToTuple res)

但是,这不会编译并出现以下错误。这次我漏掉了什么简单的东西?

src\CDCQuarterly.hs:122:20:
    Couldn't match type `(,,,,,)
                           SqlValue SqlValue SqlValue SqlValue SqlValue'
                   with `[]'
    Expected type: [(SqlValue,
                     SqlValue,
                     SqlValue,
                     SqlValue,
                     SqlValue,
                     SqlValue)]
      Actual type: (SqlValue,
                    SqlValue,
                    SqlValue,
                    SqlValue,
                    SqlValue,
                    SqlValue)
    In the second argument of `mapM', namely `(listToTuple res)'
    In a stmt of a 'do' block: mapM toResult (listToTuple res)

src\CDCQuarterly.hs:122:32:
    Couldn't match type `[SqlValue]' with `SqlValue'
    Expected type: [SqlValue]
      Actual type: [[SqlValue]]
    In the first argument of `listToTuple', namely `res'
    In the second argument of `mapM', namely `(listToTuple res)'

我可以想到两种方法来避免警告。第一种也是最简单的方法是为 toResult 添加另一个等式,它捕获所有其他大小的列表并提供信息性错误:

toResult :: [SqlValue] -> IO Course
toResult [ ... ] = do ... --Your existing code goes here
toResult xs      = error $ "toResult: List must contain exactly six values (recieved " ++ length xs ++ ")."

但是,如果您的函数设计为仅对长度为 6 的列表进行操作,我怀疑列表是否真的是此处使用的最佳数据结构。

理想情况下,您的函数的类型签名应该让阅读它的人很好地了解如何使用该函数。如果您的函数只能接受长度为六的列表,那么它的类型签名就无法传达这一点。我建议选择不具有可变长度的不同数据结构,例如 6 元组(可能与 typenewtype 组合)或自定义 data 类型。这完全消除了基于长度的模式匹配问题,并使您的函数更容易从 "outside world."

中理解

在不了解更多上下文的情况下,我无法确切地说出哪种恒定大小的数据结构最适合您的情况,但这是一个简单的示例,希望能够说明我所描述的方法的好处:

--Explicitly hexadic type signature (might benefit from some aliasing)
toResult :: (SQLValue, SQLValue, SQLValue, SQLValue, SQLValue, SQLValue) -> IO Course
toResult ( fromSql -> courseid, fromSql -> title,
           fromSql -> name,     fromSql -> version,
           fromSql -> cdate,    fromSql -> fid ) = do ...
--No need for separate equations due to constant-size container

不幸的是,如果您不能从这种情况中完全删除 List 类型,那么所有这一切都是将问题转移到其他地方,因为您最终需要第二个函数来将 List 转换为元组。 These types of functions are invariably ugly and a pain to work with.

您的函数具有 GHC 无法保证在编译时满足的要求(即,恰好传递六个值)这一事实将是不可能的。即使您 100% 认为 quickQuery 将 return 恰好包含六个元素的列表(并且您真的完全在数学上 确定这一点吗?),您仍然必须考虑事实并非如此的逻辑可能性。这意味着您必须在运行时提供一种处理这种情况的方法。

那么,当传递的值多于或少于六个时,您想做什么?当前在您的程序中如何实施错误处理?这是一个使用 error:

的潜在天真的例子
sixValues :: [SQLValue] -> 
             (SQLValue, SQLValue, SQLValue, SQLValue, SQLValue, SQLValue)
sixValues [a,b,c,d,e,f] = (a,b,c,d,e,f)
sixValues xs            = error "Wrong number of values"

虽然并不比我的第一个建议好多少,但这至少具有将长度处理逻辑与结果生成逻辑分开的优势。

我很感激我从这次经历中得到的投入和学习。但是,我已经从一个发出 compile-time 警告的工作函数取得进展,我很乐意忽略一个阻止构建输出可执行文件的编译错误。

我采纳了该线程中的一些建议,但基本上又回到了我最初关于 non-exhaustive 模式匹配的警告。最后,似乎在执行始终 returns 相同数量元素列表的数据库查询与然后将该列表用作或将该列表转换为 fixed-size collection.

每种语言都有其怪癖。也许这是 Haskell.

感谢所有的意见和指导。

戴夫·史密斯