Search code examples
haskellio

Haskell - Branch within recursive loop executes all previously visited branches in addition to the chosen one


I'm trying by doing to learn some Haskell, but have run into an issue i have no clue how to even explain. The problem point is some code to handle letting the user pick the order to visit menu options, and changing their options as often as they like before confirming. The first choice goes smooth, and subsequent selections of the same option do too, but as soon as a different selection is made, every loop from that point, regardless of whether 1 or 2 is input, the execution will go down both of the options in the function modeSelectParse (See code below). Those branches are the only calls to either of gatherSettings or mart in the program, and this behaviour persisted even when they were both stubbed out to just a print statement.

When i added more branches, all previously visited branches are run once on each iteration, in a fixed order all the time regardless of my shuffling around the order of the branches in modeSelectParse, or what number corresponds to what option.

I suspected stdin buffering being at fault, so i have tried turning that off, and various other tests on the input i got in. But nothing seemed off about the data when i tried printing it at various points in the program either.

The initial call to modeSelectInit is also not in a place where it could ever end up called more than once.

I can give more explanation and code if needed.

modeSelectInit :: DatabaseStruct -> IO (IO Settings, IO Order)
modeSelectInit db = do
  putStrLn "A lot of text"
  hFlush stdout

  let select = (return emptySettings, return [])
  input <- awaitChar ['1', '2']
  modeSelect db (modeSelectParse db input select)

modeSelect :: DatabaseStruct -> (IO Settings, IO Order) -> IO (IO Settings, IO Order)
modeSelect db select = do
  settings <- fst select
  order <- snd select

  putStrLn "A lot of text"
  hFlush stdout

  input <- awaitChar ['1', '2', '3']

  if input == '3'
  then return select
  else modeSelect (modeSelectParse db input select)

modeSelectParse :: DatabaseStruct -> Char -> (IO Settings, IO Order) -> (IO Settings, IO Order)
modeSelectParse db '1' (_, o) = (gatherSettings, o)
modeSelectParse db '2' (s, _) = (s, mart db)
awaitChar :: [Char] -> IO Char
awaitChar chars = do
  input <- getLine
  awaitChar2 input chars

awaitChar2 :: String -> [Char] -> IO Char
awaitChar2 [] chars = awaitChar chars
awaitChar2 (x: _) chars = ifThenElse (x `elem` chars) (return x) (awaitChar chars)

Solution

  • Both passing IO actions as arguments and returning nested IO actions are very unusual. (There are of course situations where that's exactly what needed, but it is not in 99% of standard, run-of-the-mill, day-to-day Haskell.) I suspect just eliminating that oddity will take care of your problem. So, try these type signatures instead:

    modeSelectInit :: DatabaseStruct -> IO (Settings, Order)
    modeSelect :: DatabaseStruct -> (Settings, Order) -> IO (Settings, Order)
    modeSelectParse :: DatabaseStruct -> Char -> (Settings, Order) -> IO (Settings, Order)
    

    Their implementations will have to be tweaked slightly to compensate, but I suspect from what I see here that you know roughly how to do that. (But if that feels hard, then definitely say -- many people here would be happy to say exactly how if you find yourself feeling stuck!)