IDL 8.4: functional programming
posted Tue 28 Oct 2014 by Michael Galloy under IDLBasics
IDL adds some functional programming syntax in 8.4:
- lambda functions/procedures (inline routines)
- filter, map, and reduce methods
Why is functional programming so good? With side-effect free functions, the order of computation is not important, making it easier to distribute computations over multiple cores. MapReduce is an increasingly popular programming model for dealing with large data in a distributed environment which uses this idea. While these additions to IDL don’t make any of this directly possible yet, they do provide some of the necessary background. And, of source, some just are happier in a functional programming environment.
Lambda functions/procedures
Lambda functions and procedures are typically short routines that are created inline and not bound to a name, i.e., the routine is attached to a variable instead of a name. For example, to define a squaring function, do:
IDL> f = lambda(x: x^2)
IDL> print, f(indgen(4))
0 1 4 9
Note that you can specify the expression in a string as well:
IDL> f = lambda('x: x^2')
Lambda expressions can also take multiple arguments:
IDL> g = lambda(x, y: x * y)
IDL> g(3, 2)
6
Lambda expressions are available in the virtual machine and can be specified by a string, making them a great way to use user-defined expressions in your application and still provide it to others using the virtual machine.
To define a lambda procedure, use LAMBDAP
:
IDL> p = lambdap(x: print, x)
IDL> p, 5
5
While lambda functions are limited to a single expression that is the return value, procedures may contain multiple statements separated with ‘&’:
Once you have defined a lambda function, there are multiple ways to call it, i.e., apply it to data:
IDL> f = lambda(x : x * x)
IDL> print, call_function(f, findgen(10))
IDL> print, f(findgen(10))
IDL> print, (findgen(10)).map(f)
We will discuss the last statement and some related techniques a bit later.
Note that f
is just a string[1]:
IDL> help, f
F STRING = 'IDL$LAMBDAF1'
Routines that accepted a string name of a routine to call should work just fine with lambda routines.
Filter, map, and reduce
Regular IDL variables[2], hashes, and lists now have filter
, map
, nestedMap
and reduce
methods to create new variables, lists, or hashes by processing the existing data in some manner.
For example, let’s create a simple list of city names:
IDL> places = list('Boulder', 'Denver', 'Fort Collins')
Now we want to create a new list with the state in the city name:
IDL> print, places->map(lambda(x: x + ', CO'))
Boulder, CO
Denver, CO
Fort Collins, CO
With filter
, we can create a new list that contains a subset of the cities which start with the letter “B”:
IDL> print, places->filter(lambda(x: strmid(x, 0, 1) eq 'B'))
Boulder
reduce
let’s us build up some cumulative value from pairs of items, such as finding a sum of an array of floating point numbers:
IDL> x = findgen(10)
IDL> print, x->reduce(lambda(x, y: x + y))
45.000000
Finally, nestedMap
does a cartesian product type of mapping between multiple (up to eight) variables, lists, or hashes. For example:
IDL m = lindgen(5)
IDL> n = lindgen(5)
IDL> print, m->nestedMap(lambda(x, y: x * y), n)
0 0 0 0 0
0 1 2 3 4
0 2 4 6 8
0 3 6 9 12
0 4 8 12 16
There is also a FILTER
keyword that filters the result:
IDL> print, m->nestedMap(lambda(x, y: x * y), n, filter=lambda(p: p mod 2))
1 3 3 9
There is plenty of more information about functional programming, but this should give you the basics of what was added in IDL 8.4.
IDL> f = lambda(x : x * x)
IDL> help, f
F STRING = 'IDL$LAMBDAF1'
IDL> g = 'IDL$LAMBDAF1'
IDL> g(findgen(3))
0.0000000 1.0000000 4.0000000
October 30th, 2014 at 5:50 pm
compile_opt strictarr (or compile_opt idl2) has to be set before lambda function call can work.
Took me a little while to figure that out though it is indeed noted on the Help page.