idl: pass keyword dynamically to isa function to test structure read by read_csv

248 views Asked by At

I am using IDL 8.4. I want to use isa() function to determine input type read by read_csv(). I want to use /number, /integer, /float and /string as some field I want to make sure float, other to be integer and other I don't care. I can do like this, but it is not very readable to human eye.

str = read_csv(filename, header=inheader)
; TODO check header
if not isa(str.(0), /integer) then stop
if not isa(str.(1), /number) then stop
if not isa(str.(2), /float) then stop

I am hoping I can do something like

expected_header = ['id',       'x',       'val']
expected_type   = ['/integer', '/number', '/float']
str = read_csv(filename, header=inheader)
if not array_equal(strlowcase(inheader), expected_header) then stop
for i=0l,n_elements(expected_type) do
  if not isa(str.(i), expected_type[i]) then stop
endfor

the above doesn't work, as '/integer' is taken literally and I guess isa() is looking for named structure. How can you do something similar?

Ideally I want to pick expected type based on header read from file, so that script still works as long as header specifies expected field.

EDIT:

my tentative solution is to write a wrapper for ISA(). Not very pretty, but does what I wanted... if there is cleaner solution , please let me know.

Also, read_csv is defined to return only one of long, long64, double and string, so I could write function to test with this limitation. but I just wanted to make it to work in general so that I can reuse them for other similar cases.

function isa_generic,var,typ
; calls isa() http://www.exelisvis.com/docs/ISA.html with keyword
; if 'n', test /number
; if 'i', test /integer
; if 'f', test /float
; if 's', test /string
  if typ eq 'n' then return, isa(var, /number)
  if typ eq 'i' then  then return, isa(var, /integer)
  if typ eq 'f' then  then return, isa(var, /float)
  if typ eq 's' then  then return, isa(var, /string)
  print, 'unexpected typename: ', typ
  stop
end
2

There are 2 answers

2
Pi Marillion On BEST ANSWER

IDL has some limited reflection abilities, which will do exactly what you want:

expected_types =  ['integer', 'number', 'float']
expected_header = ['id',      'x',      'val']
str = read_csv(filename, header=inheader)
if ~array_equal(strlowcase(inheader), expected_header) then stop
foreach type, expected_types, index do begin
    if ~isa(str.(index), _extra=create_struct(type, 1)) then stop
endforeach

It's debatable if this is really "easier to read" in your case, since there are only three cases to test. If there were 500 cases, it would be a lot cleaner than writing 500 slightly different lines.

This snipped used some rather esoteric IDL features, so let me explain what's happening a bit:

expected_types is just a list of (string) keyword names in the order they should be used.

The foreach part iterates over expected_types, putting the keyword string into the type variable and the iteration count into index.

This is equivalent to using for index = 0, n_elements(expected_types) - 1 do and then using expected_types[index] instead of type, but the foreach loop is easier to read IMHO. Reference here.

_extra is a special keyword that can pass a structure as if it were a set of keywords. Each of the structure's tags is interpreted as a keyword. Reference here.

The create_struct function takes one or more pairs of (string) tag names and (any type) values, then returns a structure with those tag names and values. Reference here.

Finally, I replaced not (bitwise not) with ~ (logical not). This step, like foreach vs for, is not necessary in this instance, but can avoid headache when debugging some types of code, where the distinction matters.

--

Reflective abilities like these can do an awful lot, and come in super handy. They're work-horses in other languages, but IDL programmers don't seem to use them as much. Here's a quick list of common reflective features I use in IDL, with links to the documentation for each:

Note: Be very careful using the execute function. It will blindly execute any IDL statement you (or a user, file, web form, etc.) feed it. Never ever feed untrusted or web user input to the IDL execute function.

1
mgalloy On

You can't access the keywords quite like that, but there is a typename parameter to ISA that might be useful. This is untested, but should work:

expected_header = ['id', 'x', 'val']
expected_type = ['int', 'long', 'float']
str = read_cv(filename, header=inheader)
if not array_equal(strlowcase(inheader), expected_header) then stop
for i = 0L, n_elemented(expected_type) - 1L do begin
  if not isa(str.(i), expected_type[i]) then stop
endfor