Implementing grad and shape transformations as a follow up to pytensor_from_scratch would highlight different approaches to graph transformation: eager (grad) and lazy via rewrites (shape).
This has links to vectorization, scalarization, logprob transformation/inference, marginalization...