Another Reason Why Function Subprocedures Should Not Modify Their Parameters
February 2, 2011 Ted Holt
I’ve never liked the idea of functions modifying their parameters. It seems to me that a function should accept zero or more input values and return one and only one value. Modifying a parameter is a roundabout way of returning another value. While looking for something in the ILE RPG reference recently, I found yet one more reason why it’s not a good idea for a function to modify a parm. This is IBM‘s example code with my modifications to make it run. Figure 189. Sample coding of a call with side effects *..1....+....2....+....3....+....4....+....5....+....6....+....7...+.... H dftactgrp(*no) actgrp(*new) D fn PR 5P 0 D parm 5P 0 D A S 5P 0 D X S 5P 0 * A is a variable. FN is procedure that modifies A. /free *inlr = *on; a = 5; x = a + fn(a); return; /end-free P fn B D fn PI 5P 0 D parm 5P 0 /free parm = parm + 1; return 2 * parm; /end-free P fn E What’s the value of X after the second EVAL? The answer is: “I don’t know.” The answer may be 17 or 18, depending on whether the system calls the function before adding, or adds before calling the function. In IBM’s words, “The order of evaluation within an expression is not guaranteed.” Little errors like this cause debugging nightmares. So what’s the solution? The solution is to return all values in modified parameters, like this: H dftactgrp(*no) actgrp(*new) D proc PR D parm 5P 0 D answer 5P 0 D A S 5P 0 D X S 5P 0 * A is a variable. PROC is procedure that modifies A. /free *inlr = *on; a = 5; proc (a: x); x += a; return; /end-free P proc B D proc PI D parm 5P 0 D answer 5P 0 /free parm = parm + 1; answer = 2 * parm; /end-free P proc E Now let’s make it a bit more practical. Why would anybody make a function subprocedure modify a parameter? Maybe out of scope creep. Suppose the original function takes a customer number and date as arguments and returns the total amount of sales for that customer on that date. DCustSalesForDay pr 9p 2 D CustNbr 6p 0 const D Date 8p 0 const D TotalSales s 9P 2 /free // call the function subprocedure TotalSales = CustSalesForDay (CusNbr: DateOfSale); ... etc ... PCustSalesForDay b D pi 9p 2 D CustNbr 6p 0 const D Date 8p 0 const D*** locals D TotSls s 9p 2 /free setll (CustNbr: Date) slshistr; dow '1'; reade (CustNbr: Date) slshistr; if %eof(); leave; endif; TotSls += InvAmt; enddo; return TotSls; /end-free P E Later, someone requests that the number of sales also be printed on the report. Since we already have a routine that’s accessing those database records, we may as well add another parameter for the count. DCustSalesForDay pr 9p 2 D CustNbr 6p 0 const D Date 8p 0 const D NbrOfSales 3P 0 D TotalSales s 9P 2 D SalesCount s 3P 0 /free TotalSales = CustSalesForDay (CusNbr: DateOfSale: SalesCount); ... etc ... PCustSalesForDay b D pi 9p 2 D CustNbr 6p 0 const D Date 8p 0 const D NbrOfSls 3P 0 D*** locals D TotSls s 9p 2 /free TotSls = *zero; NbrOfSls = *zero; setll (CustNbr: Date) slshistr; dow '1'; reade (CustNbr: Date) slshistr; if %eof(); leave; endif; TotSls += InvAmt; NbrOfSls += 1; enddo; return TotSls; /end-free P E But here’s the better way: DCustSalesForDay pr D CustNbr 6p 0 const D Date 8p 0 const D AmtOfSales 9P 2 D NbrOfSales 3P 0 D TotalSales s 9P 2 D SalesCount s 3P 0 /free CustSalesForDay (CusNbr: DateOfSale: TotalSales: SalesCount); ... etc ... PCustSalesForDay b D pi D CustNbr 6p 0 const D Date 8p 0 const D AmtOfSales 9P 2 D NbrOfSales 3P 0 /free AmtOfSales = *zero; NbrOfSales = *zero; setll (CustNbr: Date) slshistr; dow '1'; reade (CustNbr: Date) slshistr; if %eof(); leave; endif; AmtOfSales += InvAmt; NbrOfSales += 1; enddo; /end-free P E There are exceptions to this principle. For example, we’ve published two articles dealing with I/O routines that return one value and modify another. I’ve referenced them below. To see the RPG manual page that gave rise to this tip, visit the IBM i Information Center. RELATED STORIES Returning a Pointer to a Data Structure from a Function Functions Can Modify Parameters, Too
|