Skip to content

Commit 87ce823

Browse files
authored
Removed head and tail calls from codebase (#1567)
Also removed the `-Wwarn=x-partial` compilation flag on CircleCI: future uses of head/tail will be marked as errors.
1 parent e389efb commit 87ce823

File tree

14 files changed

+161
-93
lines changed

14 files changed

+161
-93
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
command: |
4747
stack --no-terminal setup
4848
rm -fr $(stack path --dist-dir) $(stack path --local-install-root)
49-
stack --no-terminal build --fast -j1 --ghc-options -Werror --ghc-options -Wwarn=x-partial
49+
stack --no-terminal build --fast -j1 --ghc-options -Werror
5050
stack build hlint
5151
stack build hpc-lcov
5252
yarn install
@@ -75,7 +75,7 @@ jobs:
7575
- run:
7676
name: Generate documentation
7777
command: |
78-
stack exec haddock -- -o doc -h --optghc=-iapp --optghc=-XOverloadedStrings --optghc=-XPartialTypeSignatures --optghc=-XScopedTypeVariables --ignore-all-exports app/Main.hs
78+
stack exec haddock -- -o doc -h --optghc=-iapp --optghc=-XOverloadedStrings --optghc=-XPartialTypeSignatures --optghc=-XScopedTypeVariables --optghc=-XLambdaCase --ignore-all-exports app/Main.hs
7979
8080
- store_artifacts:
8181
path: doc

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
- Added `Models` folder and a new `Course.hs` module within it
9292
- Added the `Graph.hs` module in the `App/Models`
9393
- Updated eslint configuration for eslint v9
94+
- Removed calls to `head` and `tail`
9495

9596
## [0.6.0] - 2024-06-24
9697

app/Controllers/Timetable.hs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Text.Blaze ((!))
2727
import qualified Text.Blaze.Html5 as H
2828
import qualified Text.Blaze.Html5.Attributes as A
2929
import Text.Read (readMaybe)
30+
import Util.Helpers
3031

3132
gridResponse :: ServerPart Response
3233
gridResponse =
@@ -132,12 +133,15 @@ getCoursesInfo courses = map courseInfo allCourses
132133
allCourses = map (T.splitOn "-") (T.splitOn "_" courses)
133134

134135
-- | Pulls either a Lecture, Tutorial or Pratical from the database.
135-
pullDatabase :: (Code, Section, Session) -> IO MeetTime'
136+
pullDatabase :: (Code, Section, Session) -> IO (Maybe MeetTime')
136137
pullDatabase (code, section, session) = runDb $ do
137138
meet <- returnMeeting code fullSection session
138-
allTimes <- selectList [TimesMeeting ==. entityKey meet] []
139-
parsedTime <- mapM (buildTime . entityVal) allTimes
140-
return $ MeetTime' (entityVal meet) parsedTime
139+
case meet of
140+
Nothing -> return Nothing
141+
Just x -> do
142+
allTimes <- selectList [TimesMeeting ==. entityKey x] []
143+
parsedTime <- mapM (buildTime . entityVal) allTimes
144+
return $ Just (MeetTime' (entityVal x) parsedTime)
141145
where
142146
fullSection
143147
| T.isPrefixOf "L" section = T.append "LEC" sectCode
@@ -150,9 +154,10 @@ pullDatabase (code, section, session) = runDb $ do
150154
type SystemTime = String
151155

152156
-- | Creates all the events for a course.
153-
getEvents :: SystemTime -> MeetTime' -> IO [String]
154-
getEvents systemTime lect = do
155-
courseInfo <- getCourseInfo lect -- Get the course information
157+
getEvents :: SystemTime -> Maybe MeetTime' -> IO [String]
158+
getEvents _ Nothing = return []
159+
getEvents systemTime (Just courseTime) = do
160+
courseInfo <- getCourseInfo courseTime -- Get the course information
156161
let startTimes = third courseInfo -- Extract start times
157162
endTimes = fourth courseInfo -- Extract end times
158163
dates = fifth courseInfo -- Extract dates
@@ -290,7 +295,7 @@ formatTimes fullTime =
290295
else hour ++ maybe "0000" formatMinutes minutes ++ "00"
291296
where
292297
hours = splitOn "." (show fullTime)
293-
hour = head hours
298+
hour = safeHead "" hours
294299
minutes = readMaybe $ hours !! 1
295300

296301
-- | The string representaion for minutes.
@@ -324,15 +329,17 @@ type EndDate = String
324329
-- | Gives the appropriate starting and ending dates for each day, in which the
325330
-- course takes place, depending on the course session.
326331
getDatesByDay :: Session -> [Time] -> IO (StartDate, EndDate)
327-
getDatesByDay session dataByDay
332+
getDatesByDay _ [] = error "Failed to fetch dates"
333+
getDatesByDay session (firstDate:_)
328334
| session == "F" = do
329335
fallStart <- fallStartDate
330336
fallEnd <- fallEndDate
331-
formatDates $ getDates fallStart fallEnd (weekDay $ head dataByDay)
337+
formatDates $ getDates fallStart fallEnd dayOfWeek
332338
| otherwise = do
333339
winterStart <- winterStartDate
334340
winterEnd <- winterEndDate
335-
formatDates $ getDates winterStart winterEnd (weekDay $ head dataByDay)
341+
formatDates $ getDates winterStart winterEnd dayOfWeek
342+
where dayOfWeek = weekDay firstDate
336343

337344
-- | Formats the date in the following way: YearMonthDayT.
338345
-- For instance, 20150720T corresponds to July 20th, 2015.

app/Database/CourseInsertion.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ getDistributionKey description_ = do
2525
-- option: keyListDistribution :: [DistributionId] <- selectKeysList [ DistributionDescription `contains'` description] []
2626
return $ case keyListDistribution of
2727
[] -> Nothing
28-
_ -> Just (head keyListDistribution)
28+
(x:_) -> Just x
2929

3030
getBreadthKey :: T.Text -> SqlPersistM (Maybe (Key Breadth))
3131
getBreadthKey description_ = do
3232
keyListBreadth :: [Key Breadth] <- selectKeysList [ BreadthDescription ==. description_ ] []
3333
-- option: selectKeysList [ BreadthDescription `contains'` description] []
3434
return $ case keyListBreadth of
3535
[] -> Nothing
36-
_ -> Just (head keyListBreadth)
36+
(x:_) -> Just x
3737

3838
-- | Inserts course into the Courses table.
3939
insertCourse :: (Courses, T.Text, T.Text) -> SqlPersistM ()

app/Database/CourseQueries.hs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,12 @@ reqsForPost post = do
5656

5757
-- | Queries the database for all information regarding a specific meeting for
5858
-- a @course@, returns a Meeting.
59-
returnMeeting :: T.Text -> T.Text -> T.Text -> SqlPersistM (Entity Meeting)
59+
returnMeeting :: T.Text -> T.Text -> T.Text -> SqlPersistM (Maybe (Entity Meeting))
6060
returnMeeting lowerStr sect session = do
61-
entityMeetings <- selectList [MeetingCode ==. T.toUpper lowerStr,
61+
selectFirst [MeetingCode ==. T.toUpper lowerStr,
6262
MeetingSection ==. sect,
6363
MeetingSession ==. session]
6464
[]
65-
return $ head entityMeetings
6665

6766
-- ** Other queries
6867

app/Database/Tables.hs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,14 @@ instance FromJSON SvgJSON
224224

225225
instance ToJSON Meeting where
226226
toJSON = genericToJSON defaultOptions {
227-
fieldLabelModifier =
228-
(\field -> toLower (head field): tail field) .
229-
drop 7
230-
}
227+
fieldLabelModifier =
228+
lowerFirst .
229+
drop 7
230+
}
231+
where
232+
lowerFirst :: [Char] -> String
233+
lowerFirst [] = ""
234+
lowerFirst (fieldHead: fieldTail) = toLower fieldHead: fieldTail
231235

232236
instance FromJSON Meeting where
233237
parseJSON = withObject "Expected Object for Lecture, Tutorial or Practical" $ \o -> do

app/Models/Graph.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Database.Persist.Sqlite (Entity, PersistEntity, PersistValue (PersistInt6
1212
selectList, (<-.), (==.))
1313
import Database.Tables hiding (paths, shapes, texts)
1414
import Svg.Builder (buildEllipses, buildPath, buildRect, intersectsWithShape)
15+
import Util.Helpers
1516

1617
getGraph :: T.Text -> IO (Maybe Value)
1718
getGraph graphName = runDb $ do
@@ -31,7 +32,7 @@ getGraph graphName = runDb $ do
3132

3233
let
3334
keyAsInt :: PersistEntity a => Entity a -> Integer
34-
keyAsInt = fromIntegral . (\(PersistInt64 x) -> x) . head . keyToValues . entityKey
35+
keyAsInt = fromIntegral . (\(PersistInt64 x) -> x) . safeHead (PersistInt64 0) . keyToValues . entityKey
3536

3637
graphtexts = map entityVal sqlTexts
3738
rects = zipWith (buildRect graphtexts)

app/Svg/Builder.hs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import qualified Data.Text as T
2323
import Database.DataType
2424
import Database.Tables hiding (shapes, texts)
2525
import Svg.Parser (matrixPointMultiply)
26+
import Util.Helpers
2627

2728
-- * Builder functions
2829

@@ -41,7 +42,7 @@ buildPath rects ellipses entity elementId
4142
pathTarget = ""}
4243
| otherwise =
4344
let coords = pathPoints entity
44-
start = head coords
45+
start = safeHead (0.0, 0.0) coords
4546
end = last coords
4647
nodes = rects ++ ellipses
4748
sourceNode =
@@ -144,9 +145,9 @@ intersects :: Double -- ^ The region's width.
144145
intersects width height (rx, ry) offset (px, py) =
145146
let dx = px - rx
146147
dy = py - ry
147-
in dx >= -1 * offset &&
148+
in dx >= -offset &&
148149
dx <= width + offset &&
149-
dy >= -1 * offset &&
150+
dy >= -offset &&
150151
dy <= height + offset;
151152

152153
-- | Determines if a point is contained in a shape.

app/Svg/Generator.hs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import Text.Blaze.Svg.Renderer.String (renderSvg)
2929
import qualified Text.Blaze.Svg11 as S
3030
import Text.Blaze.Svg11 ((!))
3131
import qualified Text.Blaze.Svg11.Attributes as A
32+
import Util.Helpers
3233

3334

3435
-- | This is the main function that retrieves a stored graph
@@ -45,9 +46,25 @@ buildSVG :: T.Text -- ^ The name of the graph that is being built.
4546
-> Bool -- ^ Whether to include inline styles.
4647
-> IO ()
4748
buildSVG graphName courseMap filename styled = runDb $ do
48-
gIds :: [Key Graph] <- selectKeysList [GraphTitle ==. graphName] []
49-
let gId = if null gIds then toSqlKey 1 else head gIds
49+
maybeGraph :: Maybe (Entity Graph) <- selectFirst [GraphTitle ==. graphName] []
50+
case maybeGraph of
51+
Nothing -> return ()
52+
Just val -> do
53+
liftIO $ buildSVGHelper courseMap filename styled (entityVal val) (entityKey val)
5054

55+
buildSVGHelper :: M.Map T.Text T.Text -- ^ A map of courses that holds the course
56+
-- ID as a key, and the data-active
57+
-- attribute as the course's value.
58+
-- The data-active attribute is used in the
59+
-- interactive graph to indicate which
60+
-- courses the user has selected.
61+
-> String -- ^ The filename that this graph will be
62+
-- written to.
63+
-> Bool -- ^ Whether to include inline styles.
64+
-> Graph
65+
-> Key Graph
66+
-> IO ()
67+
buildSVGHelper courseMap filename styled sqlGraph gId = runDb $ do
5168
sqlRects :: [Entity Shape] <- selectList
5269
[ShapeType_ <-. [Node, Hybrid],
5370
ShapeGraph ==. gId] []
@@ -56,7 +73,6 @@ buildSVG graphName courseMap filename styled = runDb $ do
5673
sqlEllipses :: [Entity Shape] <- selectList
5774
[ShapeType_ ==. BoolNode,
5875
ShapeGraph ==. gId] []
59-
sqlGraph :: [Entity Graph] <- selectList [GraphId ==. gId] []
6076
let courseStyleMap = M.map convertSelectionToStyle courseMap
6177
texts = map entityVal sqlTexts
6278
-- TODO: Ideally, we would do these "build" steps *before*
@@ -75,8 +91,8 @@ buildSVG graphName courseMap filename styled = runDb $ do
7591
regionTexts = filter (not .
7692
intersectsWithShape (rects ++ ellipses))
7793
texts
78-
width = graphWidth $ entityVal (head sqlGraph)
79-
height = graphHeight $ entityVal (head sqlGraph)
94+
width = graphWidth sqlGraph
95+
height = graphHeight sqlGraph
8096
stringSVG = renderSvg $ makeSVGDoc courseStyleMap
8197
rects
8298
ellipses
@@ -89,7 +105,7 @@ buildSVG graphName courseMap filename styled = runDb $ do
89105
liftIO $ writeFile filename stringSVG :: SqlPersistM ()
90106
where
91107
keyAsInt :: PersistEntity a => Entity a -> Integer
92-
keyAsInt = fromIntegral . (\(PersistInt64 x) -> x) . head . keyToValues . entityKey
108+
keyAsInt = fromIntegral . (\(PersistInt64 x) -> x) . safeHead (PersistInt64 0) . keyToValues . entityKey
93109

94110
convertSelectionToStyle :: T.Text -> T.Text
95111
convertSelectionToStyle courseStatus =

0 commit comments

Comments
 (0)