1

Here is my 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)

Any call to this function that does not pass a parameter that is a list of exactly six SqlValue values is an error.

Compiling this returns the non-exhaustive pattern matching warning:

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

I know that I can just ignore the warning, but I am trying to learn how to do this Haskell stuff correctly.

Any and all suggestions are appreciated.

Dave

Update...

Based upon the suggestions presented here, I have changed my code to the present form.

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)

However, this won't compile with the following error. What simple thing am I missing this time?

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)'
dacDave
  • 232
  • 1
  • 12

2 Answers2

2

I can think of two ways to avoid the warning. The first and simplest way is add another equation for toResult that catches lists of all other sizes and provides an informative error:

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

But if your function is designed to only operate on lists of length six, I doubt that a list is really the best data structure to use here.

Your function's type signature should ideally give the people reading it a pretty good indication of how to use the function. If your function can only accept lists of length six, then its type signature fails to communicate that. I recommend choosing a different data structure that does not have variable length, such as a 6-tuple (potentially combined with type or newtype) or a custom data type. This completely eliminates the issue of pattern matching based on length, and makes your function easier to understand from the "outside world."

Without knowing more context I can't tell exactly what constant-size data structure would be most appropriate in your case, but here's a bare-bones example that hopefully illustrates the benefits of the approach I've described:

--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

Unfortunately, if you can't fully remove the List type from the situation, then all this accomplishes is moving the problem somewhere else because you end up needing a second function to convert from a List to a Tuple. These types of functions are invariably ugly and a pain to work with.

It's going to be impossible to get around the fact that your function has requirements (namely, being passed exactly six values) that GHC cannot guarantee are being met at compile-time. Even if you're 100% that quickQuery will return a list of exactly six elements (and are you really, entirely, mathematically certain of this?), you still have to account for the logical possibility of this not being the case. This means you have to provide, at runtime, a way of dealing with this case.

So, what do you want to do when more or less than six values are passed? How is error-handling currently implemented in your program? Here is a potentially naive example using 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"

While not much better than my very first suggestion, this does at least have the advantage of separating the length-handling logic from the result-generating logic.

Community
  • 1
  • 1
ApproachingDarknessFish
  • 14,133
  • 7
  • 40
  • 79
  • GHC does not guarantee you deterministic behavior if you use `error` (or pure exceptions in general). In the above code you can use `throwIO`, which does not have this problem. – Simon Hengel Apr 02 '16 at 06:28
  • Thank you for getting me closer. OK, I took the advice offered and rewrote the function to take a 6-tuple as a parameter rather than a list of SqlValue, i,e, [SqlValue]. However, the source of this parameter is a call to quickQuery, which returns [SqlValue]. Now I have no warning on my toResult function, but how do I take the [SqlValue] returned from quickQuery and convert it into a 6-tuple of SqlValue to pass it to my function? – dacDave Apr 03 '16 at 04:26
  • @dacDave Hmm, that makes things much more complicated. Unfortunately Haskell does not provide a convenient method for converting from Lists to Tuples (see http://stackoverflow.com/q/2921345/1530508). I'll edit the answer to reflect this point and elaborate slightly, and offer a partial solution. – ApproachingDarknessFish Apr 03 '16 at 07:38
  • 1
    Since I am calling a method, quickQuery, that performs IO, I suspect that this cannot be mathematically proven. If nothing else, we cannot mathematically guarantee that the network connection to the database server is intact. However, given no error from the database, I am executing a statically defined query that is hard-coded into the logic. Therefore, I can be assured that an error-free query will always return a list of exactly 6 elements. I will add the conversion method, as you suggested. Thank you, again. – dacDave Apr 03 '16 at 13:33
1

I appreciate the input and learning that I have received from this experience. However, I have progressed from a working function that issued a compile-time warning that I was comfortable ignoring to a compile error that prevents building the output executable.

I have incorporated a couple of the suggestions from this thread, but have basically gone back to my original warning about a non-exhaustive pattern match. It seems, in the end, that there is an irreconcilable incongruity between executing a database query that always returns a list of the same number of elements and then using that list as, or converting that list to, a fixed-size collection.

Every language has its quirks. Maybe this is one for Haskell.

Thanks for all the input and guidance.

Dave Smith

dacDave
  • 232
  • 1
  • 12