HiveBrain v1.2.0
Get Started
← Back to all entries
debugMinor

Interpreting database values as times and integers

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
andinterpretingdatabasevaluestimesintegers

Problem

I am working through Chapter 10 of Haskell Programming from First Principles.

I've gotten the code to work, but I am uneasy about the fact that a few of my functions - namely, extractUTCTime and extractInteger - are not total functions. There is valid input for which those functions are bottom. I've added the code below, but I was wondering what strategies are available for making extractUTCTime and extractInteger safe. (One strategy that I do know of is changing the type signature of these functions so that they return a value of type Maybe: extractUTCTime :: DatabaseItem -> Maybe UTCTime. I was wondering if there was anything else that can be done).

```
import Data.Time

data DatabaseItem = DbString String | DbNumber Integer | DbDate UTCTime deriving (Eq, Ord, Show)

theDatabase :: [DatabaseItem]
theDatabase = [DbDate (UTCTime (fromGregorian 1911 5 1) (secondsToDiffTime 34123)), DbNumber 9001, DbString "Hello, world!", DbDate (UTCTime (fromGregorian 1921 5 1) (secondsToDiffTime 34123))]

filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate [] = []
filterDbDate dataBase = [extractUTCTime item | item Bool
isDbDate (DbDate _) = True
isDbDate _ = False

extractUTCTime :: DatabaseItem -> UTCTime
extractUTCTime (DbDate utcData) = utcData

filterDbNumber :: [DatabaseItem] -> [Integer]
filterDbNumber [] = []
filterDbNumber dataBase = [extractInteger item | item Integer
extractInteger (DbNumber int) = int

isDbNumber :: DatabaseItem -> Bool
isDbNumber (DbNumber _) = True
isDbNumber _ = False

mostRecent :: [DatabaseItem] -> UTCTime
mostRecent dataBase = foldr max y ys
where dates = filterDbDate dataBase
y = head dates
ys = tail dates

sumDb :: [DatabaseItem] -> Integer
sumDb dataBase = foldr (+) 0 dbNumbers
where dbNumbers = filterDbNumber dataBase

avgDb :: [DatabaseItem] -> Double
avgDb dataBase = dividend / divisor
where dividend = fromInteg

Solution

I was wondering if there was anything else that can be done

Well, morally, no. You can either return a nullable value (Maybe UTCTime) or lie (read "1970-01-01 00:00:00 UTC" :: UTCTime) but you can't turn a DbNumber 1 :: DatabaseItem into a UTCTime both unconditionally and truthfully.

The good news is, if you hit on the right set of higher-order functions you don't need to. Maybe values are very easy to work with when you know the extra tools available in Data.Maybe.

Taking as a whole this set of functions related to dates—

filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate [] = []
filterDbDate dataBase = [extractUTCTime item | item  Bool
isDbDate (DbDate _) = True
isDbDate _          = False

extractUTCTime :: DatabaseItem -> UTCTime
extractUTCTime (DbDate utcData) = utcData

mostRecent :: [DatabaseItem] -> UTCTime
mostRecent dataBase = foldr max y ys
                where dates = filterDbDate dataBase
                      y     = head dates
                      ys    = tail dates


We'll begin by throwing out isDbDate and extractUTCTime and writing the DatabaseItem -> Maybe UTCTime function you avoided.

dbDate :: DatabaseItem -> Maybe UTCTime
dbDate (DbDate utc) = Just utc
dbDate _            = Nothing


Now pulling from Data.Maybe, we'll replace filterDbDate with an equivalent code fragment that utilizes mapMaybe.

-- filterDbDate :: [DatabaseItem] -> [UTCTime]
-- filterDbDate db =
     mapMaybe dbDate db


We can use that definition in mostRecent, a function from the standard Prelude, and point free style to achieve a one liner that more than just being clever, emphasizes the power of abstraction in producing concise code that will be instantly comprehensible to other Haskell programmers.

mostRecent :: [DatabaseItem] -> UTCTime
mostRecent = maximum . mapMaybe dbDate


mostRecent still commits the sin of using a partial function though, maximum throws an exception (much like your use of head and tail did before) when the filtered list is empty. Until such time as partial functions are excised from base (and O, do we all lust for and fear such a tumultuous day) you should familiarize yourself with the safe package, which has no dependencies besides base, and provides many alternatives to the partial functions we all know and love.

import Safe.Foldable (maximumMay)

mostRecent :: [DatabaseItem] -> Maybe UTCTime
mostRecent = maximumMay . mapMaybe dbDate

Code Snippets

filterDbDate :: [DatabaseItem] -> [UTCTime]
filterDbDate [] = []
filterDbDate dataBase = [extractUTCTime item | item <- dataBase, isDbDate item]

isDbDate :: DatabaseItem -> Bool
isDbDate (DbDate _) = True
isDbDate _          = False

extractUTCTime :: DatabaseItem -> UTCTime
extractUTCTime (DbDate utcData) = utcData

mostRecent :: [DatabaseItem] -> UTCTime
mostRecent dataBase = foldr max y ys
                where dates = filterDbDate dataBase
                      y     = head dates
                      ys    = tail dates
dbDate :: DatabaseItem -> Maybe UTCTime
dbDate (DbDate utc) = Just utc
dbDate _            = Nothing
-- filterDbDate :: [DatabaseItem] -> [UTCTime]
-- filterDbDate db =
     mapMaybe dbDate db
mostRecent :: [DatabaseItem] -> UTCTime
mostRecent = maximum . mapMaybe dbDate
import Safe.Foldable (maximumMay)

mostRecent :: [DatabaseItem] -> Maybe UTCTime
mostRecent = maximumMay . mapMaybe dbDate

Context

StackExchange Code Review Q#146305, answer score: 3

Revisions (0)

No revisions yet.