Python IR redesign to use pointers instead of Lets

(continuing discussion from design meeting yesterday.)

The Python IR should be redesigned to represent bindings using multiple references to the same IR, rather than explicit Lets, which present cumbersome and opaque interfaces to users.

For instance, the following:

foo = ht.x / ht.y
ht = ht.annotate(z = foo*foo)

Should compute ht.x / ht.y only once.

We already generate the IR we want, but during serialization, we inline everything. The solution is to change the serialization method to traverse the IR tree to pull up duplicated references as Lets.

The Let should probably be emitted at the most recent common ancestor of the duplicated references (which should preserve the validity of the IR we generate in Python, without having to worry about building use-def chains).