Re: scope of variable in NCL

From: Saji N Hameed <saji_at_nyahnyahspammersnyahnyah>
Date: Tue Oct 27 2009 - 00:09:43 MDT

Dear All,

Firstly, this issue is closed -- the mistake was at my end!

Secondly, I would like to share a small ruby program that I cooked up
so that I can clean up the mistakes made as part of my
assumption about variable scopes in NCL. Essentially the code
parses a file containing NCL code, detects functions and checks if their
parameters are corrupted within the function definition block. Since I
do not prefer my parameters to be altered, i have written it in such
a way that the 'rogue parameters' are copied just after the begin
statement to a temporary variable and then copied back from the temporary
variable just before the 'return' statement.

I was able to find a few (thankfully not many) of my libraries that contained
such rogue functions. Interestingly, the NCL bundled "gsn_code.ncl" contains
such a 'rogue function' by the name "compute_hist_vals" where one of the
input parameters 'nbinlocs' is altered within the function (perhaps this
is an intended behaviour?)

If it is of use to somebody, please use it...

Best wishes,

saji

--
Example usage of the program is shown below
#-------- start of example script that uses rogue_ncl_func.rb
require 'rogue_ncl_func'
require 'pathname'
  # check all NCL files under the directories writers, readers, helpers,
  # ncl_libs and time
%w{writers readers helpers ncl_libs time}.each do |root_dir|
  Pathname.glob("#{root_dir}/*.ncl") do |infil|
    NCLParser.new(infil).parse
  end
end
#-------- end of script
# -- start of program rogue_ncl_func.rb
class NCLParser
  def initialize(infil,write_all=false)
    @fin=File.open(infil,'r')
    @fpath=File.dirname(infil)
    @fout=File.open(File.join(
              @fpath,File.basename(infil,".ncl")+"_modified.ncl"),"w")
    @write_all=write_all
    @found_rogue_func=false
    @rog_fun=0
  end
  def write_out?()
   return(true) if @write_all | @found_rogue_func
   false
  end
  def insert_line(where,identifier,section,line,comment="")
    if identifier==:current
      idx=section[:code].length 
    else
      found=section[:code].select {|str| str =~ /^\s*#{identifier}/}
      idx=section[:code].index(found[0])
    end
    where==:after ? idx+=1 : idx-=1
    spacer=(section[:code][idx])[/^\A\s*/].length
    comment="\s"*spacer+";"+comment+"\n"
    section[:code][idx] = comment+"\s"*spacer+line+"\n"+section[:code][idx]
  end
  def initialize_hash
    {:code => [], :rog_arg => []}
  end
  def parse
    section=initialize_hash
    @fin.each do  |line|
      section[:code] << line
      case line
      when /((function|procedure)\b)(\s+\w+)(\()(\w+(,\s*\w+)*)/
        section[:type],section[:name]=$1,$3
        section[:args]=$5.split(",")
        next
      when /^\s*return\s*\(/
        comment="copying back function argument from temporary variable"
        section[:rog_arg].uniq! # remove duplicates
        section[:rog_arg].each do |arg| 
          if @found_rogue_func
            insert_line(:before,:current,section, "#{arg}=__#{arg}",comment)
          end
        end
        next
      when /^\s*end\W+$/
        write_out(section) if write_out?
        section=initialize_hash
        @found_rogue_func = false       
        next
      end
      
      case k=section[:type]
      when /function/
        comment="copying function argument to temporary variable"
        section[:args].each do |arg| 
          if line[/^\s*#{arg}\s*=/]
            @found_rogue_func=true
            break if section[:rog_arg].any? {|aa| aa == arg}
            insert_line(:after,"begin",section,"__#{arg}=#{arg}",comment)
            section[:rog_arg] << arg
          end 
        end
      end
    end
    clean_up
  end
  def write_out(data)
    fn_name=data[:name] 
    @fout.puts "load \"#{@fin.path}\"" if @rog_fun == 0
    @fout.puts "undef(\"#{fn_name}\")"
    @fout.puts data[:code]
    @rog_fun+=1
  end
  def clean_up
    puts "Rogue function detected in #{@fin.path}" unless @rog_fun == 0
    @fout.close 
    File.unlink(@fout.path) if @rog_fun == 0
  end
end
* Saji N Hameed <saji@apcc21.net> [2009-10-22 14:32:08 +0900]:
> Dear David, Jonathan and others,
> 
> Thanks for the explanations, although you are out of town. 
> (Also Jonathan, thanks for the sample code.
> I am sorry I did not reply, as I had caught a bad cold and has been out of
> action for a while).
> 
> Firstly, it is my mistake that I did not read the documentation
> carefully and made assumptions (the most common package used by most of
> us, Grads, passes arguments by value and some other languages I
> use such as C or ruby also allows for function parameters to be
> local if needed). 
> It is NCL's rule and I should play by it. I hope it is a good
> experience to others too.
> 
> > The rationale for passing arguments by reference in NCL is that  
> > arguments might be large data arrays and it would be expensive to copy  
> > them. Unlike C, NCL (in the interest of keeping things simple) does not 
> > have a pointer type, so the only way to avoid copying is to pass  
> > arguments by reference.
> 
> I am not a computer scientist and I may be completely wrong and
> I apologize beforehand in this case. In 
> my understanding although we can pass arguments by value in C, 
> the  exception is for arrays (since
> a string is an array of characters, same for strings too), in that
> arrays passed in as arguments are considered as pointers by the recieving
> function. In this case, if one modifies a subset of the array(string),
> the changes will be seen outside the function. I was wondering
> if the same paradigm may be applied with benefit to 
> the NCL language (simple arguments
> are local and arrays are global)? I imagine that most people may not want
> 'side-effects' and may prefer this way. And if somebody needs changes
> in his/her variable made inside the function to be available 
> during the next call, maybe there could be a 'static' keyword as in C.
> 
> Best regards,
> 
> saji
> 
> * David Brown <dbrown@ucar.edu> [2009-10-21 14:37:30 -0600]:
> 
> > Hi Saji,
> >
> > I'd like to return to this question, because I may have been somewhat  
> > misleading in my response of Oct 16:
> >
> > In particular, I need to note the difference between what in Fortran is 
> > called the 'actual' argument versus the 'dummy' argument.  In the second 
> > of your examples the function
> > 'testme' has 3 dummy arguments a, b, and c. These are used in the  
> > definition of the function. When called it is passed the actual  
> > arguments a, 1, and 2.  Thus in the example the symbol 'a' is both an  
> > actual argument and a dummy argument,  somewhat obscuring the true  
> > behavior of NCL. Here is an alternate version of your example:
> >
> > a=5
> > foo = 42
> > function testme(foo,b,c)
> > begin
> >   foo=2
> > return(1)
> > end
> > print(""+a)
> > print(""+testme(a,1,2))
> > print(""+a)
> > print(""+foo)
> >
> > The output is as follows:
> >
> > (0)	5
> > (0)	1
> > (0)	2
> > (0)	42
> >
> > So yes, an actual argument can be modified within a function, but a  
> > global variable that just happens to have the same name as a dummy  
> > argument is not affected by calling the function. So in that sense, the 
> > dummy arguments are inherently 'local'. Presumably if you call a  
> > function using a variable that has been defined globally you know you  
> > are doing so. Any function or procedure that potentially changes an  
> > input argument should definitely be documented as doing so.
> >
> > The rationale for passing arguments by reference in NCL is that  
> > arguments might be large data arrays and it would be expensive to copy  
> > them. Unlike C, NCL (in the interest of keeping things simple) does not 
> > have a pointer type, so the only way to avoid copying is to pass  
> > arguments by reference.
> >
> > The only difference between a function and a procedure in NCL is that a 
> > function returns a value.
> >  -dave
> >
> >
> > On Oct 17, 2009, at 6:42 PM, Saji wrote:
> >
> >> Hi Dave and Wei and others,
> >>
> >> Thanks for the feedback and clarification. I understand it
> >> , but doesn't it illustrate a situation, where some unsuspecting
> >> user accidentally uses the same name somewhere in his script
> >> as the function argument and lands up a nasty surprise. If the
> >> library developer cannot ensure the 'local' scope of the arguments
> >> within the function,  it is a bit inconvenient, as the developer
> >> has to be careful not to change the arguments that are passed on
> >> to the function. I still wonder why this behaviour is implemented
> >> in NCL functions, as a function is supposed to return a single data  
> >> object.
> >> ...why should it change other variables or arguments that it does not
> >> return. Also this means that there is no real difference between a
> >> so called 'procedure' and 'function'. If I am a paranoid user, I would 
> >> have
> >> to check all function definitions, before I feel confident to use  
> >> them.
> >>
> >> I wonder if it will be possible to rethink this design issue in NCL at
> >> least for functions --
> >>
> >> Best regards,
> >> saji
> >>
> >>
> >> * David Brown <dbrown@ucar.edu> [2009-10-16 09:29:30 -0600]:
> >>
> >>> Hi Saji,
> >>> Wei is quite correct about this case. Arguments are passed by
> >>> reference and the variables they reference can be modified inside
> >>> the function. The 'local' statement has no meaning for arguments.
> >>> You can ignore the last message I sent. I apologize that I did not
> >>> notice that 'a' was being used as an argument to the function.
> >>> -dave
> >>>
> >>> On Oct 16, 2009, at 8:09 AM, Wei Huang wrote:
> >>>
> >>>> Saji,
> >>>>
> >>>> That is because you use "a" as an argument element, and local.
> >>>>
> >>>> If you do "print("" + test(0,1,2))", then you can not change "a"
> >>>> out side your function.
> >>>>
> >>>> Wei Huang
> >>>> huangwei@ucar.edu
> >>>> VETS/CISL
> >>>> National Center for Atmospheric Research
> >>>> P.O. Box 3000 (1850 Table Mesa Dr.)
> >>>> Boulder, CO 80307-3000 USA
> >>>> (303) 497-8924
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> On Oct 15, 2009, at 11:03 PM, Saji N Hameed wrote:
> >>>>
> >>>>> Even declaring "a" as local does not seem to work :(
> >>>>> ncl 1> function test(a,b,c)
> >>>>> ncl 2> local a,b,c
> >>>>> ncl 3> begin
> >>>>> ncl 4>   a=-999
> >>>>> ncl 5> return(1)
> >>>>> ncl 6> end
> >>>>> ncl 7> print(""+test(a,1,2))
> >>>>> (0)	1
> >>>>> ncl 8> print(""+a)
> >>>>> (0)	-999
> >>>>>
> >>>>> * Saji N Hameed <saji@apcc21.net> [2009-10-16 13:59:15 +0900]:
> >>>>>
> >>>>>> Dear NCL Developers,
> >>>>>>
> >>>>>> I suppose that the following should not happen. Isn't it a bug?
> >>>>>> We are told that variables inside a function have local scope, and
> >>>>>> this unusual feature can lead to a lot of headaches. Any  
> >>>>>> solutions,
> >>>>>> other than "local a" or using another variable name than "a"
> >>>>>>
> >>>>>> Copyright (C) 1995-2009 - All Rights Reserved
> >>>>>> University Corporation for Atmospheric Research
> >>>>>> NCAR Command Language Version 5.1.1
> >>>>>> The use of this software is governed by a License Agreement.
> >>>>>> See http://www.ncl.ucar.edu/ for more details.
> >>>>>> ncl 0> a=5
> >>>>>> ncl 1> function testme(a,b,c)
> >>>>>> ncl 2> begin
> >>>>>> ncl 3>   a=2
> >>>>>> ncl 4> return(1)
> >>>>>> ncl 5> end
> >>>>>> ncl 6> print(""+a)
> >>>>>> (0)	5
> >>>>>> ncl 7> print(""+testme(a,1,2))
> >>>>>> (0)	1
> >>>>>> ncl 8> print(""+a)
> >>>>>> (0)	2
> >>>>>>
> >>>>>> Thanks,
> >>>>>> saji
> >>>>>> --
> >>>>>>
> >>>>>>
> >>>>>> _______________________________________________
> >>>>>> ncl-talk mailing list
> >>>>>> List instructions, subscriber options, unsubscribe:
> >>>>>> http://mailman.ucar.edu/mailman/listinfo/ncl-talk
> >>>>>>
> >>>>>
> >>>>> -- Saji N. Hameed
> >>>>>
> >>>>> APEC Climate Center          				
> >>>>> 1463 U-dong, Haeundae-gu,                               +82 51 745
> >>>>> 3951
> >>>>> BUSAN 612-020, KOREA                    		saji@apcc21.net
> >>>>> Fax: +82-51-745-3999
> >>>>>
> >>>>>
> >>>>>
> >>>>> _______________________________________________
> >>>>> ncl-talk mailing list
> >>>>> List instructions, subscriber options, unsubscribe:
> >>>>> http://mailman.ucar.edu/mailman/listinfo/ncl-talk
> >>>>
> >>>> _______________________________________________
> >>>> ncl-talk mailing list
> >>>> List instructions, subscriber options, unsubscribe:
> >>>> http://mailman.ucar.edu/mailman/listinfo/ncl-talk
> >>>
> >>>
> >>
> >> -- 
> >> Saji N. Hameed
> >>
> >> APEC Climate Center          				
> >> 1463 U-dong, Haeundae-gu,                               +82 51 745  
> >> 3951
> >> BUSAN 612-020, KOREA                    		saji@apcc21.net
> >> Fax: +82-51-745-3999
> >>
> >>
> >>
> >> _______________________________________________
> >> ncl-talk mailing list
> >> List instructions, subscriber options, unsubscribe:
> >> http://mailman.ucar.edu/mailman/listinfo/ncl-talk
> >
> >
> 
> -- 
> Saji N. Hameed
> 
> APEC Climate Center          				
> 1463 U-dong, Haeundae-gu,                               +82 51 745 3951
> BUSAN 612-020, KOREA                    		saji@apcc21.net
> Fax: +82-51-745-3999
> 
> 
> 
> _______________________________________________
> ncl-talk mailing list
> List instructions, subscriber options, unsubscribe:
> http://mailman.ucar.edu/mailman/listinfo/ncl-talk
> 
-- 
Saji N. Hameed
APEC Climate Center          				
1463 U-dong, Haeundae-gu,                               +82 51 745 3951
BUSAN 612-020, KOREA                    		saji@apcc21.net
Fax: +82-51-745-3999
_______________________________________________
ncl-talk mailing list
List instructions, subscriber options, unsubscribe:
http://mailman.ucar.edu/mailman/listinfo/ncl-talk
Received on Tue Oct 27 00:10:06 2009

This archive was generated by hypermail 2.1.8 : Thu Oct 29 2009 - 10:19:30 MDT