Problem: ------- > module Calc (calc,calcr,calcr2) where (a) Write a function calc :: String -> String that receives a string consisting in arithmetic expressions separated by newlines, and returns the string consisting in the results of the corresponding operations, separated by newlines. Specifically, calc "5+2\n3*4\n7-2\nq\n" should return "7\n12\n5\n". Each arithmetic operation has the form const1 op const2 where const1 and const2 are integer constants, and op might be either +, -, or *. The function should consider only that portion of the input string ended by the occurrence of the sequence "\nq\n". The input is considered correct, you don't need to write any error handling. You may use the following function: > stringToInteger :: String -> Integer > stringToInteger = read Such a function receives a string that is the representation of an integer and returns the corresponding integer. (b) Submit to GHCi the following expression: interact calc Then, type "5+3" (without the surrounding quotes) and press "return." After this, type "q" (again, without quotes) and press "return." Comment the result. Infer the functionality of the predefined function interact. (c) Modify the program written for point (a), obtaining the function calcr, such that it can handle up to 10 registers. Specifically, the arithmetic operations now have the form: const_or_reg_1 op const_or_reg_2 (*) where const_or_reg_i can be either an integer constant as above, or the name of a register. A register is denoted by Rk, where k can be any number from 0 to 9. Furthermore, all the registers are initialized to 0, and the following expression changes the value of one register: Rk = const1 (**) Such an operation returns the new value stored in the register. The input for the program should be now of the form "e1\ne2\n...\nej\nq\n" where each e1... ej is of the form (*) or (**). For example, calcr "R1=200\nR2=400\nR1+R2\nq\n" should return "200\n400\n600\n". You may use the following function: > stringToInt :: String -> Int > stringToInt = read The function is similar to stringToInteger, except that is returns a value of type Int. Solution: -------- (a) Splitting the input into lines: The function readln receives a string and returns the lines in the input as a list of strings. The function terminate when it encounters a "\nq\n" sequence in the input. First, a reasonably elegant version that uses the predefined functions takeWhile and dropWhile: > readln :: String -> [String] > readln ('q':'\n':rest) = [] > readln a_str = (takeWhile (/= '\n') a_str) : > (readln (tail (dropWhile (/= '\n') a_str))) Another version that does not use the predefined functions might be: > readln1 :: String -> [String] > readln1 ('q':'\n':rest) = [] > readln1 a_str = (findEol a_str):(readln1 (dropEol a_str)) > where findEol ('\n':rest) = [] > findEol (a:rest) = a:(findEol rest) > dropEol ('\n':rest) = rest > dropEol (a:rest) = dropEol rest However, note that the computation performed by readln and readln1 is also performed by the predefined function "lines," except that our function terminate when "\nq\n" is reached. Thus, a third implementation might be: > readln2 :: String -> [String] > readln2 = (takeWhile (/= "q")).lines Now, let's process a line. One should identify first the operator and the operands... > type Op = Char > > parseExp :: String -> (Integer,Op,Integer) > parseExp a_str = (stringToInteger (takeWhile notAnOp a_str), > head rest, > stringToInteger (tail rest)) > where rest = dropWhile notAnOp a_str > notAnOp x = x/='+' && x/='-' && x/='*' ...and then perform the actual computation: > compute :: (Integer,Op,Integer) -> Integer > compute (x,o,y) > | o=='+' = x+y > | o=='-' = x-y > | o=='*' = x*y A whole line is processed therefore by the function > processLine :: String -> String > processLine = show.compute.parseExp The core function of the program can be thus written as: > process :: [String] -> [String] > process = map processLine Finally, the result (expressed as a list of strings) should be converted back to the form of text lines separated by newlines: > writeln :: [String] -> String > writeln = concat.(map (++"\n")) or, if one does not want to use concat, > writeln1 :: [String] -> String > writeln1 = (foldl (++) []).(map (++"\n")) Note that the same result is accomplished by the predefined function "unlines." In conclusion, the main function is > calc :: String -> String > calc = writeln.process.readln (b) An example of interactive run for calc is: Calc> interact calc 3-4 -1 5*6 30 q Calc> where the lines "3-4," "5*6," and "q" were typed at the keyboard. That is, the function interact takes as argument a function f::String -> String, and passes to f whatever string is typed in from the standard input. Then, the result of f is printed to the standard output. One should also note that interact prints out any partial result, as soon as it is available. As a side note, the type of interact is interact :: (String -> String) -> IO () (c) The result is accomplished by rewriting some of the functions developed for the solution of point (a). Those functions that are rewritten will have an 'r' appended to their name. One should introduce the concept of "state." Such a state will keep the values stored in the 10 registers. The easiest way of accomplishing this is to use a list l, where (l !! i) stores the value of register i: > type State = [Integer] The functions that manipulate the states: getReg accesses a register, and setReg stores a new value into a register: > getReg :: Int -> State -> Integer > setReg :: Int -> Integer -> State -> State > getReg idx state = state !! idx > setReg idx val state = take idx state ++ val : drop (idx+1) state Now, parseExp should consider the new expression types. Generally, a value is now either an integral constant or a register: > data Value = Val Integer | Reg Int Note that the predefined type Either could have been used, but the program is more clear if we introduce a new type. > parseExpr :: String -> (Value,Op,Value) > parseExpr a_str = (toVal first, head rest, toVal (tail rest)) > where rest = dropWhile notAnOp a_str > first = takeWhile notAnOp a_str > toVal (' ':smth) = toVal smth > toVal ('R':reg) = Reg (stringToInt reg) > toVal ('r':reg) = Reg (stringToInt reg) > toVal val = Val (stringToInteger val) > notAnOp x = x/='+' && x/='-' && x/='*' && x/='=' The function toVal strips the argument of the eventual leading blanks, then converts it to a value (either register or constant). The function compute should be changed in order to account for states. Specifically, in addition to the expression to be computed, it should receive a state, and return the state together with the result: > computer :: State -> (Value,Op,Value) -> (State,Integer) > computer s (Val x, o, Val y) = (s, compute (x,o,y)) > computer s (Reg x, '=', Val y) = (setReg x y s, y) > computer s (Reg x, o, Val y) = (s, compute (getReg x s, o, y)) > computer s (Val x, o, Reg y) = (s, compute (x, o, getReg y s)) > computer s (Reg x, o, Reg y) = (s, compute (getReg x s, o, getReg y s)) Note that the only expression that changes the current state is of the form "Ri = const." The function processLine therefore becomes: > processLiner :: (State,String) -> (State,String) > processLiner (s,str) = apply (id,show) (computer s (parseExpr str)) > where apply (f,g) (x,y) = (f x, g y) Note that we convert the integral result to a string, but the state is unchanged. The predefined function id simply returns whatever it receives. A possible definition of it is id :: a -> a id x = x Thus, the core function is: > processr :: State -> [String] -> [String] > processr s [] = [] > processr s (x:xs) = snd result : processr (fst result) xs > where result = processLiner (s,x) One cannot use the map function anymore, since the state ("snd result" in the above definition) should be passed between two calls of processLiner. Therefore, we provided an explicit recursive definition. Finally, the main function is almost unchanged, except that an initial state should be provided: > calcr :: String -> String > calcr = writeln.(processr initState).readln > where initState = [0 | y <- [1..]] The initial state stores zero in all the registers, and is defined using a list comprehension. As a side note, the above program is not restricted to 10 registers. Indeed, it accepts any register number that can be represented by a value of type Int. However, since the registers are retrieved using the !! operator (which performs a linear search), the use of a register with a high index is computationally expensive. However, the definition of the initial state as an infinite list does not add computational cost, because Haskell is lazy evaluated. Another note: As the function is defined above, the call "interact calcr" does not offer any clue that the program expects you to enter an expression. Here is a variant that prints out a prompt: > writelnPrompt :: [String] -> String > writelnPrompt = concat.(map (++"\nReady: ")) > calcr1 :: String -> String > calcr1 = writelnPrompt.(processr initState).readln > where initState = [0 | y <- [1..]] The result is: Calc> interact calcr1 1+2 3 Ready: r1 = 23 23 Ready: q Calc> We still have a problem, namely that the first time an expression is expected, no prompt is printed. We can correct this by explicitely returning a result that has the prompt at the beginning: > calcr2 :: String -> String > calcr2 s = "Ready: " > ++ (writelnPrompt.(processr initState).readln) s > where initState = [0 | y <- [1..]] We finally get the desired result: Calc> interact calcr2 Ready: r1 = 10 10 Ready: R1+1 11 Ready: q Calc> Finally, one should note that the eventual errors in the input are handled (although not in a very explicit manner) by the error cases for the standard functions that are used in the program. As a consequence, errors are not recoverable, the program returns _|_ for any illegal input.