Home > front end >  How can I "save" a variable when repeating a function?
How can I "save" a variable when repeating a function?

Time:05-01

Right now I'm trying to make a program that gives the sum of all numbers entered thus far. currently I have this start of the code:

import System.Exit

main :: IO()
main = do
    a <- getLine
    if a == "EXIT_PROGRAM"
    then
        exitWith ExitSuccess
    else
        print(a)
    main

It's working fine right now but you can probably tell it's not what I want. To get this I would have to save the previous sum and then add it to a, then save that and repeat the function. How can I do this? Thank you.

CodePudding user response:

Found a way, probably should have figured it out sooner. I made a separate function that unlike main could take input. then I fed the product into it.

import System.Exit

multiplier :: Integer -> IO()
multiplier prevProduct = do 
    a <- getLine
    if a == "EXIT_PROGRAM"
    then
        exitWith ExitSuccess
    else
        return()
    let b = (read a :: Integer) * prevProduct
    print(b)
    multiplier b

main :: IO()
main = do
    multiplier 1

also I changed it so it multiplied instead of added

CodePudding user response:

The implementation suggested in another answer has the correct behaviour, but strikes me as perhaps not entirely idiomatic. In Haskell return doesn't mean what it does in most other languages, so the use of return () seems odd. Likewise, using exitWith strikes me as using exceptions for flow control - a technique that is frowned upon in most languages (not just Haskell).

Furthermore, the program will not be able to handle any errant input such as foo, exit, or even EXIT PROGRAM. In all such cases, it's going to crash:

> multiplier 1
2
2
3
6
EXIT PROGRAM
*** Exception: Prelude.read: no parse

As chi suggests in a comment, using recursion would be more idiomatic, but in Haskell, you often do that by having a base case that exits cleanly.

The desired behaviour is almost a Read Eval Print Loop (REPL), except that the evaluation function is given on beforehand. So perhaps, readPrintLoop might be an appropriate name. Here's one way to do it:

readPrintLoop :: (Show a, Read b) => (a -> b -> a) -> a -> IO ()
readPrintLoop eval acc = do
  s <- getLine
  let m = readMaybe s
  case m of
    Just x -> do
      let y = eval acc x
      print y
      readPrintLoop eval y
    Nothing -> return ()

This action uses readMaybe from Text.Read to parse the input with the option of failing gracefully. As a consequence, any input that doesn't parse (such as foo, exit or EXIT_PROGRAM) will exit the loop.

It first reads the input using getLine, parses with readMaybe, and then pattern-matches the Maybe value m.

If m is Nothing, the action exits cleanly with ().

If m is a Just value, it uses eval (a function argument) to calculate the new accumulated value, prints it, and recurses with the new accumulator.

Since the OP describes accumulating a sum, and the answer from the same user accumulates a product, I've made the readPrintLoop flexible by enabling the caller to define the function being used.

You can even use it to accumulate decimal numbers as well (since any Read instance will do):

> readPrintLoop ( ) 0.0
1.1
1.1
2.3
3.4
exit
>

If, on the other hand, you want main to accumulate an integer product, you may define it like this:

main :: IO ()
main = do
  readPrintLoop (*) 1
  • Related