CSC/ECE 517 Fall 2007/wiki1 3 b6: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 95: Line 95:


In the initial form of the program, the programmer must specify the log level parameter for each trace statement they wish to create.  This approach has two drawbacks:
In the initial form of the program, the programmer must specify the log level parameter for each trace statement they wish to create.  This approach has two drawbacks:
1) The programmer must know what log level corresponds to what level of trace (0=debug, 1=info, 2=warning, 3=error, etc).  If they make a mistake in their numbering, the trace will not be correct.
<br>
2) The code is hard to read since it is not obvious just by looking at a piece of code, whether a message is going to be printed as a debug statement, informational statement, warning, etc.
<ol>
 
<li>The programmer must know what log level corresponds to what level of trace (0=debug, 1=info, 2=warning, 3=error, etc).  If they make a mistake in their numbering, the trace will not be correct.
<li>The code is hard to read since it is not obvious just by looking at a piece of code, whether a message is going to be printed as a debug statement, informational statement, warning, etc.
</ol>


Now the same program, but with 4 new methods defined by currying the original logMessage method.  The new methods are:
Now the same program, but with 4 new methods defined by currying the original logMessage method.  The new methods are:
1) debug - print a debug level message
<ol>
2) print - print an informational message
<li> debug - print a debug level message
3) warn - print a warning level message
<li> print - print an informational message
4) error - print an error level message
<li> warn - print a warning level message
 
<li> error - print an error level message
</ol>
The actual currying is done in the Logger class, and the invocation of these methods can be seen in the body of the main program and the Account class.
The actual currying is done in the Logger class, and the invocation of these methods can be seen in the body of the main program and the Account class.



Revision as of 04:57, 12 September 2007

Example use of Currying in Ruby by Ben Parees

The following pair of programs demonstrates the value of currying functions in Ruby. The program itself implements a simple account balance management tool which allows for withdrawals and deposits as specified by the user. In the first program, a Logger class is defined with a single function, "logMessage" which takes an integer log level parameter, and the message to be logged. Depending on the log level specified, and the overall logging level set in the Logger class, the message may or may not be displayed to the user. If the message is to be displayed, it will be prefixed with a string indicating the message's relative importance.

The purpose of this class is to allow programmers to easily reconfigure their program to provide varying degrees of debugging output without changing code. They can simply change the log level that is configured, and automatically filter out messages which are below that log level. In addition, messages are automatically tagged with a string indicating their severity, which makes analyzing logs much easier.

Here is the initial version of the program:

class Logger
        @logLevel
        attr_accessor :logLevel
        def initialize(n)
                @logLevel=n
        end

        def logMessage(n,msg)
                if(n>=@logLevel)
                        prepend=case n
                                when 0 then 'DEBUG: '
                                when 1 then ''
                                when 2 then 'WARNING: '
                                when 3 then 'ERROR: '                                
                        end
                        puts prepend+msg
                end

        end
end
class Account
        @balance
        @logger
        attr_reader :balance
        def initialize(n,logger)
                @balance=n
                @logger=logger
                logger.logMessage(0,"initialized account with balance of #{n}");
        end


        def getAmount
          @logger.logMessage(0,"enter getAmount method")
          amount=gets.chomp!.to_f
          @logger.logMessage(0,"Got amount #{amount}")
          if(amount<0)
                @logger.logMessage(3,"Amount is less than zero, amount will be set to zero")
                amount=0
          end
          return amount
        end

        def withdraw
          @logger.logMessage(0,"enter withdraw method")
          @logger.logMessage(1,"How much would you like to withdraw?")
          amount=getAmount
          @logger.logMessage(0,"withdrawing amount of #{amount}")
          @balance=@balance-amount
          if(@balance<0)
                @logger.logMessage(2,"Account is overdrawn")
          end
          @logger.logMessage(0,"new balance is #{@balance}")
        end

        def deposit
          @logger.logMessage(0,"enter depost method")
          amount=getAmount
          @logger.logMessage(0,"depositing amount of #{amount}")
          @balance=@balance+amount
          @logger.logMessage(0,"new balance is #{balance}")
        end

end
action="d"
logger=Logger.new(0)
account=Account.new(100,logger)


while action!="q"

        logger.logMessage(1,"Balance=#{account.balance}, would you like to withdraw, deposit, or quit [w/d/q]?")

        action=gets.chomp!
        logger.logMessage(0,"user chose action #{action}")
        if action=="w"
          account.withdraw
        elsif action=="d"
          account.deposit
        elsif action=="q"
          logger.logMessage(1,"goodbye")
        end
end


In the initial form of the program, the programmer must specify the log level parameter for each trace statement they wish to create. This approach has two drawbacks:

  1. The programmer must know what log level corresponds to what level of trace (0=debug, 1=info, 2=warning, 3=error, etc). If they make a mistake in their numbering, the trace will not be correct.
  2. The code is hard to read since it is not obvious just by looking at a piece of code, whether a message is going to be printed as a debug statement, informational statement, warning, etc.

Now the same program, but with 4 new methods defined by currying the original logMessage method. The new methods are:

  1. debug - print a debug level message
  2. print - print an informational message
  3. warn - print a warning level message
  4. error - print an error level message

The actual currying is done in the Logger class, and the invocation of these methods can be seen in the body of the main program and the Account class.

class Logger
        @logLevel
        @debug
        @print
        @warn
        @error
        attr_accessor :logLevel
        def initialize(n)
                @logLevel=n
                @debug=lambda{|msg| logMessage(0,msg)}
                @print=lambda{|msg| logMessage(1,msg)}
                @warn= lambda{|msg| logMessage(2,msg)}
                @error=lambda{|msg| logMessage(3,msg)}
        end

        def logMessage(n,msg)
                if(n>=@logLevel)
                        prepend=case n
                                when 0 then 'DEBUG: '
                                when 1 then ''
                                when 2 then 'WARNING: '
                                when 3 then 'ERROR: '
                        end
                        puts prepend+msg
                end

        end

        def debug(msg)
                @debug.call(msg)
        end
        def print(msg)
                @print.call(msg)
        end
        def warn(msg)
                @warn.call(msg)
        end
        def error(msg)
                @error.call(msg)
        end


end

class Account
        @balance
        @logger
        attr_reader :balance
        def initialize(n,logger)
                @balance=n
                @logger=logger
                logger.debug("initialized account with balance of #{n}");
        end


        def getAmount
          @logger.debug("enter getAmount method")
          amount=gets.chomp!.to_f
          @logger.debug("Got amount #{amount}")
          if(amount<0)
                @logger.error("Amount is less than zero, amount will be set to zero")
                amount=0
          end
          return amount
        end

        def withdraw
          @logger.debug("enter withdraw method")
          @logger.print("How much would you like to withdraw?")
          amount=getAmount
          @logger.debug("withdrawing amount of #{amount}")
          @balance=@balance-amount
          if(@balance<0)
                @logger.warn("Account is overdrawn")
          end
          @logger.debug("new balance is #{@balance}")
        end

        def deposit
          @logger.debug("enter depost method")
          amount=getAmount
          @logger.debug("depositing amount of #{amount}")
          @balance=@balance+amount
          @logger.debug("new balance is #{balance}")
        end

end


action="d"
logger=Logger.new(0)
account=Account.new(100,logger)


while action!="q"

        logger.print("Balance=#{account.balance}, would you like to withdraw, deposit, or quit [w/d/q]?")

        action=gets.chomp!
        logger.debug("user chose action #{action}")
        if action=="w"
          account.withdraw
        elsif action=="d"
          account.deposit
        elsif action=="q"
          logger.print("goodbye")
        end
end

Notice how much easier it is to follow the flow of the program now through the clear labelling of debug/print/warning/error statements. In addition, observe how simple it would be to add a new trace statement at any log level simply by invoking the desired method rather than having to look up the appropriate log level value.


--Bmparees 00:48, 12 September 2007 (EDT)