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.
(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
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).