I've been trying to learn about static analysis of applicative functors. Many sources say that an advantage of using them over monads is the susceptibility to static analysis.
However, the only example I can find of actually performing static analysis is too complicated for me to understand. Are there any simpler examples of this?
Specifically, I want to know if I can performing static analysis on recursive applications. For example, something like:
y = f <$> x <*> y <*> z
When analyzing the above code, is it possible to detect that it is recursive on y? Or does referential transparency still prevent this from being possible?
Applicative functors allow static analysis at runtime. This is better explained by a simpler example.
Imagine you want to calculate a value, but want to track what dependencies that value has. Eg you may use
IO ato calculate the value, and have a list ofStringsfor the dependencies:Now we can easily make this an instance of
FunctorandApplicative. The functor instance is trivial. As it doesn't introduce any new dependencies, you just need to map over therunInputvalue:The
Applicativeinstance is more complicated. thepurefunction will just return a value with no dependencies. The<*>combiner will concat the two list of dependencies (removing duplicates), and combine the two actions:With that, we can also make an
Input aan instance of Num ifNum a:Nexts, lets make a couple of Inputs:
Finally, lets make a value that uses some inputs:
This is a pretty cool value. Firstly, we can find the dependencies of
portfolioValueas a pure value:That is the static analysis that
Applicativeallows - we know the dependencies without having to execute the action.We can still get the value of the action though:
Now, why can't we do the same with
Monad? The reason isMonadcan express choice, in that one action can determine what the next action will be.Imagine there was a
Monadinterface forInput, and you had the following code:Now, how can we calculate the dependencies of
newPortolio? It turns out we can't do it without using IO! It will depend on the most popular stock, and the only way to know is to run the IO action. Therefore it isn't possible to statically track dependencies when the type uses Monad, but completely possible with just Applicative. This is a good example of why often less power means more useful - as Applicative doesn't allow choice, dependencies can be calculated statically.Edit: With regards to the checking if
yis recursive on itself, such a check is possible with applicative functors if you are willing to annotate your function names.