I'm relatively new to Haskell, and I'm trying to build a terminal user interface with brick. I'd like to record a timestamp every time a user input is given. To do this I wrote the following functions:
handleInputEvent :: TestState -> BrickEvent n e -> EventM n (Next TestState)
handleInputEvent s i =
case i of
VtyEvent vtye ->
case vtye of
EvKey KBS [] -> handleBackSpaceInput s
EvKey (KChar 'q') [MCtrl] -> halt s
EvKey (KChar c) [] -> handleTextInput s c
_ -> continue s
_ -> continue s
handleTextInput :: TestState -> Char -> EventM n (Next TestState)
handleTextInput s c =
case c of
' ' -> do
let cursor = text s
case nonEmptyCursorSelectNext cursor of
Nothing -> continue s
Just cursor' -> continue $ s {text = cursor'}
_ -> do
let tstamp = getCurrentTime
let cursor = text s
let cur_word = nonEmptyCursorCurrent cursor
let new_word = TestWord {word = word cur_word, input = input cur_word ++ [c]}
let new_text = reverse (nonEmptyCursorPrev cursor) ++ [new_word] ++ nonEmptyCursorNext cursor
let test_event = TestEvent {timestamp = tstamp, correct = isInputCorrect cur_word c}
case NE.nonEmpty new_text of
Nothing -> continue s
Just ne -> do
case makeNonEmptyCursorWithSelection (cursorPosition cursor) ne of
Nothing -> continue s
Just ne' -> continue $ s {text = ne', tevents = test_event : tevents s}
data TestEvent = TestEvent
{ timestamp :: IO UTCTime,
correct :: Bool
}
When I now try to evaluate the difference between the first and last timestamp I get almost 0, no matter how long the program runs.
ui = do
initialState <- buildInitialState 50
endState <- defaultMain htyper initialState
startTime <- timestamp (head (tevents endState))
stopTime <- timestamp (last (tevents endState))
let (timespan, _) = properFraction (diffUTCTime stopTime startTime)
print timespan
I think this is because getCurrentTime returns IO UTCTime
but the handler Functions themselves are not IO Functions, so the timestamps are only evaluated during the ui
block. Is there any way to correctly implement this functionality in brick without rebuilding the entire code?
let tstamp = getCurrentTime
says “define an action tstamp :: IO UTCTime
which is the same as the action getCurrentTime :: IO UTCTime
”. You store this value in the timestamp :: IO UTCTime
field of every TestEvent
, so this:
startTime <- timestamp (head (tevents endState))
stopTime <- timestamp (last (tevents endState))
Ends up exactly equivalent to this:
startTime <- getCurrentTime
stopTime <- getCurrentTime
And I expect it’s obvious why you see a near-0 duration between them!
Instead, you should make the type of the timestamp
field just UTCTime
, and execute getCurrentTime
in your event handler using liftIO
to run an IO
action inside Brick’s EventM
:
tstamp <- liftIO getCurrentTime
Then you can extract the start and end times without I/O:
let startTime = timestamp (head (tevents endState))
let stopTime = timestamp (last (tevents endState))
This is a common struggle for people new to Haskell: IO X
isn’t a value of type X
that came from doing I/O, it’s a program that may use I/O to make a value of type X
.