Haskell Auto-Generated code improvements

In the auto-generated code for many Haskell puzzels, you can’t access the initialization input (the input before turn 1) from the main loop.

Suggested fix: replace the loop function with a forever $ do block. All other languages use a while (true) for the game loop, and the forever function is a better way to emulate that than explicit recursion.

Example (Taken from Don’t panic):

Replace this:

main :: IO ()
main = do
    -- ...
    input_line <- getLine
    let input = words input_line
    let nbfloors = read (input!!0) :: Int -- number of floors
    -- ...
    let nbelevators = read (input!!7) :: Int -- number of elevators
    
    -- ...

    loop

loop :: IO ()
loop = do
    input_line <- getLine
    let input = words input_line
    let clonefloor = read (input!!0) :: Int -- floor of the leading clone
    let clonepos = read (input!!1) :: Int -- position of the leading clone on its floor
    let direction = input!!2 -- direction of the leading clone: LEFT or RIGHT

    -- TODO Can't access initialization variables. For example:
    hPrint stderr nbfloors -- XXX can't access nbfloors
    
    -- action: WAIT or BLOCK
    putStrLn "WAIT"
    
    loop

With this:

main :: IO ()
main = do
    -- ...
    input_line <- getLine
    let input = words input_line
    let nbfloors = read (input!!0) :: Int -- number of floors
    -- ...
    let nbelevators = read (input!!7) :: Int -- number of elevators
    
    -- ...

    forever $  do
        input_line <- getLine
        let input = words input_line
        let clonefloor = read (input!!0) :: Int -- floor of the leading clone
        let clonepos = read (input!!1) :: Int -- position of the leading clone on its floor
        let direction = input!!2 -- direction of the leading clone: LEFT or RIGHT
        
        -- TODO Now we can access initialization vars:
        hPrint stderr nbfloors
        
        -- action: WAIT or BLOCK
        putStrLn "WAIT"
2 Likes

Hi, we updated the code generation. Please check if this is what you expected.

Thanks for reporting the issue.

2 Likes

Looks good. Thank you :slight_smile:

You made one thing easier (access to initialization input), thank for that! However, with “forever” code structure it is more difficult to pass anything to the next turn. With loop function it is as easy as adding new function paramter. How to do it with “forever” structure? I had to reimplement loop fnction in my last code for Kutulu contest.

2 Likes

I’m pretty sure you did slightly more changes to the startup code to reach the gold league.

Sure 8). Just would like to learn what is the right way to pass it within forever (if any. I am not that good with do notation)

With forever, there isn’t, it’s all workarounds.

With ugly IORefs:

stateRef <- newIORef s0
forever $ do
  state <- readIORef stateRef
  (...)
  writeIORef stateRef state'

or with an ugly state transformer:

flip evalStateT s0 $ forever $ do
  state <- get
  lift $ do
    (...)
  put state'

or with just-as-ugly fake recursion:

flip fix s0 $ \loop state -> do
  (...)
  loop state'

Oops, I dropped forever there. Hey, once we’re down to that, why not use actual recursion?

main :: IO ()
main = do
  input_line <- getLine
  (...)      
  loop s0 where
   loop state = do
    input_line <- getLine
    (...)
    loop state'

Disclaimer: some of these might be slightly more tongue-in-cheek than others.

But really, I’m with Aries here. The Haskell template is unusable. So don’t use it! It’s only a real nuisance to clashing.

2 Likes