# Shifts & Lifts Interpretation For inlining lifts, we use: $$\begin{equation} \mathrm{deref}\Bigr(\mathrm{shift}\bigl(o_1, …\bigr)\bigl(\mathrm{lift}(f)(x)\bigr)\Bigl) \equiv f\bigl(\mathrm{shift}(o_1, …)(x)\bigr), \tag{1}\label{inline} \end{equation}$$ which gives $$\begin{equation} \mathrm{deref}\bigl(\mathrm{lift}(f)(x)\bigr) \equiv f(x) \tag{2}\label{inline-no-shifts} \end{equation}$$ if there are no offsets. Using $\eqref{inline}$ and $\eqref{inline-no-shifts}$, we derive: $$\begin{align} \mathrm{deref}\Bigr(\mathrm{shift}\bigl(o_1, …\bigr)\bigl(\mathrm{lift}(f)(x)\bigr)\Bigl) &\equiv f\bigl(\mathrm{shift}(o_1, …)(x)\bigr), \\ \mathrm{deref}\Bigr(\mathrm{shift}\bigl(o_1, …\bigr)\bigl(\mathrm{lift}(f)(x)\bigr)\Bigl) &\equiv \mathrm{deref}\Bigl(\mathrm{lift}\bigl(f\bigr)\bigl(\mathrm{shift}(o_1, …)(x)\bigr)\Bigr), \\ \mathrm{shift}\bigl(o_1, …\bigr)\bigl(\mathrm{lift}(f)(x)\bigr) &\equiv \mathrm{lift}\bigl(f\bigr)\bigl(\mathrm{shift}(o_1, …)(x)\bigr) \\ \tag{3}\label{inline-derived} \end{align}$$ and further $$\begin{align} \mathrm{deref}\bigl(\mathrm{lift}(\mathrm{deref})(x)\bigr) &\equiv \mathrm{deref}(x) \\ \mathrm{lift}(\mathrm{deref})(x) &\equiv x. \tag{4} \end{align}$$ Additionally, for shifts, we use: $$\begin{equation} \mathrm{shift}\bigl(o_1, …\bigr)\bigl(\mathrm{shift}(p_1,…)(x)\bigr) \equiv \mathrm{shift}(p_1, …, o_1, …)(x). \tag{5} \end{equation}$$ Now, assume $f = \lambda(x).\mathrm{deref}\bigl(\mathrm{shift}(\mathrm{E2V}, 0)(x)\bigr)$ with type $\mathrm{Iterator}[T, \mathrm{Edge}] → T$ or maybe $\mathrm{Iterator}[T, \mathrm{Edge}] → \mathrm{Value}[T, \mathrm{Vertex}]$ if we want to keep the location type of values. In any case, $\mathrm{lift}(f)$ obviously has the type $\mathrm{Iterator}[T, \mathrm{Edge}] → \mathrm{Iterator}[T, \mathrm{Vertex}]$. So, if we look at the expression $$\begin{equation} \mathrm{shift}(\mathrm{V2E}, 0\bigr)\bigl(\mathrm{lift}(f)(y)\bigr) \equiv \mathrm{shift}(\mathrm{V2E}, 0\bigr)\biggl(\mathrm{lift}\Bigl(\lambda(x).\mathrm{deref}\bigl(\mathrm{shift}(\mathrm{E2V}, 0)(x)\bigr)\Bigr)(y)\biggr), \end{equation}$$ it is clear that the type of $y$ is $\mathrm{Iterator}[T, \mathrm{Edge}]$, the type of $\mathrm{lift}(f)(y)$ is $\mathrm{Iterator}[T, \mathrm{Vertex}]$, and the type of the full expression is $\mathrm{Iterator}[T, \mathrm{Edge}]$. But now, from $\eqref{inline-derived}$, we get $$\begin{equation} \mathrm{shift}(\mathrm{V2E}, 0\bigr)\biggl(\mathrm{lift}\Bigl(\lambda(x).\mathrm{deref}\bigl(\mathrm{shift}(\mathrm{E2V}, 0)(x)\bigr)\Bigr)(y)\biggr) \\ \hspace{5em}\equiv \mathrm{lift}\Bigl(\lambda(x).\mathrm{deref}\bigl(\mathrm{shift}(\mathrm{E2V}, 0)(x)\bigr)\Bigr)\bigl(\mathrm{shift}(\mathrm{V2E}, 0)(y)\bigr). \end{equation}$$ That is, we want to evaluate $\mathrm{shift}(\mathrm{V2E}, 0)(y)$, but the location type of $y$ is $\mathrm{Edge}$, thus the expression is invalid. ## Post-meeting thoughts (Enrique): I haven't verified if this new suggestion works for all our known examples and use cases, but I'm writing it here in case you want to give it a thought during my day off, while the last discussion is still fresh. ### Iterators Let's define _Iterator_ as a generic type with 3 type parameters `It[I, P, DT]`: - `I`: type of the current position. - `P`: type of the target position on the field referenced by the iterator. - `DT`: scalar data type of the elements of the field. A naive implementation of `It` could be: ```python class It[I, P, DT]: pos: I field: Field[P, DT] def shift(conn: Callable[[I], J], it: It): return it(pos=conn(it.pos), field=it.context) # def lift(f: Stencil): # return lambda it: It(pos=it.pos, field=f(it.field) # def lifted_f(y: It[Edge, Vertex, f64]): # return it(pos=it.pos, field=field(domain=Edge, f(field.data)) ``` It's easy to see intuitively that an iterator can only be safely derreferenced when `I` == `P`. In this formulation,`shift()` has the expected signature: ```python shift(I->J)(It[I, P, DT]): It[J, P, DT] ``` A `Stencil` is a function from iterators to values. Let's take the example of `f` below and annotate it with the right types: ```python= E2V: Edge -> Vertex # The type of `y` is constrained by the `shift()` # and `deref()` operations in the body of the function def f(y: It[Edge, Vertex, f64]) -> f64: shifted: It[Vertex, Vertex, f64] = shift(E2V)(y) return deref(shifted) ``` Given this function `f`, the problem is how to define the type of `lift(f)`. I have now strong reasons to suggest the following (being `I` a generic type variable as above): ```python lift(f): It[I, Vertex, f64] -> It[I, Edge, f64] ``` and therefore `lift()` has the following generic signature: ```python lift: (It[I, P, DT1] -> DT2) -> (It[J, P, DT1] -> It[J, I, DT2]) ``` So `lift(f)` does not change the type of the current position of the function arguments, but it changes the type of their target positions. ### Fields A _Field_ is a **Profunctor**, which can be intuitively defined as a generic type with two type parameters `a` and `b` usually representing some kind of mapping from an `a` input value to a `b` output value (this is not the real definition, just an intuition). Functions can be combined with profunctors to form new profunctors. At the output type `b`, functions are composed as usual; at the input type `a`, functions are composed in the opposite direction. ![Profunctors diagram](https://raw.githubusercontent.com/hablapps/DontFearTheProfunctorOptics/master/diagram/profunctor.svg)