Small routines in a Functional Style

Field's standard python library contains a few short routines and classes designed to help you write denser code in a hurry, or reduce the number of times you have to execute things in order to have them change. These are simple but hopefully helpful pieces of code, that are documented here firstly for the sake of having them documented anywhere; and secondly to hopefully inspire other, similar, disruptive and dangerous Python tricks.

Lazy classes

(If you are an artist whose coding skills come mainly from Java, the following paragraphs might be a little disorienting. On the other hand, if you've done any computer science, I apologize for giving your secrets away.)

Think hard about the following code

a = 3
x = trans(lambda : a)
y = x+3
print y    # prints 6
a = 2
print y    # prints 5

trans is a function that produces objects that evaluate lazily. Instead of x+3 evaluating to 6 it evaluates to an object that is simply holding onto lambda : a, + and 3. It is not a number, but a recipe for making a number. Whenever pressed, this object pretends to be 6, but if it can get away with it continues to spread throughout the evaluation. It understands common base types such as int, float, long, Vector2, Vector3, Vector4, and it can masquerade as them.

What is this good for? This is a useful tool for changing your mind after "execution" time. In many cases you can set up some complicated nest of Python code and, in a moment of inspiration send, rather than a number, a trans and parameterize said code in without any refactoring. It's also very useful in the case where you have embedded GUI elements. Rather than "evaluating" to text, some elements can evaluate to trans objects. Changing the slider then can effect code far-far away, without having to execute that code (and design it such) over and over again as you drag the slider around. An analogy: rather than sending someone (a function) a letter in an envelope (a number), you are sending them a cell phone (a trans) in an envelope that lets you send text messages to them.

It's worth pointing out that this magic stops at the Java/Python boundary. To bind dynamic values to Java methods, one needs different, more careful techniques.

Another similar function is transFilter

timesTwo = transFilter(lambda a : a*2)
print timesTwo(2) # prints 4

squared = transFilter(lambda a : a*a)
print squared(3) # prints 9

print (timesTwo+squared)(2) # prints 8
print (timesTwo*2+squared)(2) # prints 12
print (timesTwo*squared)(2) # prints 16

As before, we've overloaded the usual mathematical operations to do the expected things to our functions. But we'll overload an additional operator to do something a little unexpected:

print timesTwo(3) # 6
print (timesTwo<<2)(3) # 10
print (timesTwo<<squared)(3) # 18

This operator << changes what happens to the input. Imagine a graph of timesTwo — input along the x-axis, output along the y-axis. In the case of <<2 operator slides the graph to the left by 2 units of x. In the case of we have something which is actually remaps (by filtering through squared) the input altogether. Further:

q = 4
mulQ = transFilter(lambda a : a*trans(lambda : q))
print mulQ(3) # 12
q = 3
print mulQ(3) # 9

As you'd expect (while it is, of course, unnecessary in this short example, you could have written lambda a : a*q, it's useful to know that you can mix the objects caused to be propagated by trans and transFilter). In case you are wondering you can << by a trans as well (and transFilter understands lambdas).

What's this good for? Once you have one of these, uses for them begin to spring out. First of all, the graph GUI widget returns one of these, and thus is deeply penetrating just like trans for the sliders. It turns out that passing windowing functions around and manipulating them, scaling them, dilating them and windowing them a little more becomes incredibly important for doing programmatic key-framing kinds of things — that is finding a design style that is as direct as setting keys in a timeline but has the features and advantages of writing code.

One last thing, with this use-case in mind, it's sometimes handy to be able to ask for a function that just evaluates this remapping of the inputs. For example, if all of your primative window objects have support over 0->1, and you'd like to know where you are in this original "timebase"

timesTwo = transpFilter(lambda a : a*2)
timesTwoShifted = timesTwo<<2
timesTwoShiftedAndScaled = timesTwoShifted<<(lambda x : x*2)
print timesTwoShiftedAndScaled(0) # 4
print timesTwoShiftedAndScaled.input(0)  # 2

Wrapping lists

Two shorter "magic classes". wl (standing for 'wrap list') is a list (literally, it extends list) and dispatches anything that it makes sense to dispatch to every member in the list, and returns wl's of any return values.

print wl([1,2,3])+2 # wl([3,4,5])
print wl( [Vector3(1,2,3), Vector3(-1, -2, -3), Vector4(0,1,0,1)]).x # wl([1, -1, 0])
print wl( [Vector3(0.5, 0, 0), Vector3(-0.5, 0, 0)] ).clone().normalize() # wl([Vector3(1, 0, 0), Vector3(-1, 0, 0)])

To some extent, then, one can use wl lists of some object anywhere where you might perform an operation on any particular object. Anything list-y gets dispatched to the list superclass, so you can still iterate over them and so on.

The second class is called first and it's like all except that it only computes things until some member of the list returns something "true" (in the python sense of non-zero-ness).