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 actiontstamp :: IO UTCTime
which is the same as the actiongetCurrentTime :: IO UTCTime
”. You store this value in thetimestamp :: IO UTCTime
field of everyTestEvent
, so this:Ends up exactly equivalent to this:
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 justUTCTime
, and executegetCurrentTime
in your event handler usingliftIO
to run anIO
action inside Brick’sEventM
:Then you can extract the start and end times without I/O:
This is a common struggle for people new to Haskell:
IO X
isn’t a value of typeX
that came from doing I/O, it’s a program that may use I/O to make a value of typeX
.