Basics

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

  1. And not special in any way. For example, the following works fine: ??

  2. Regular variables have methods? More on this in a later post. ??