The most significant speedups we've seen is in finding places where we can skip the wrapper and call the actual function directly. This happens when you call a function with exactly the number of arguments it needs.
In order to do this, we need to adjust function declarations so that the original function can be called either in the standrd 'wrapped' way, or directly.
This has lead to dramatic speedups in some cases, especially when a large number of smaller functions are called and the overhead of calling twice as many functions is significant.
As well, it has a really interesting characteristic in that it makes the initial size of the generated JS **larger**, but usually results in a **smaller** minified asset size.
We generate two definitions for a function, but in most cases a function is either always partially applied, or always called with the full number of arguments.
If a function is always called with the full number of arguments, the minifier can eliminate our wrapped version (`F2(MyFunction_fn)`) and _also_ eliminate the `A2` call, which is explicitly smaller than before.
Higher order functions like `List.map` have a hard time taking advantage of the direct function calls because we don't know the arity of the function within the `List.map` call.
- More work is needed to make parsing an existing project more robust and also to discover what approach for representing the shapes actually produces the best benefit.
There may be a nice trade off here of using `InlineMode.UsingConsFunc`, but only inlining at most 20 elements or something, and then using `List_fromArray` after that.
Simply creating a new record and copying each field manually is significantly faster than and of these transformations.(~9x in chrome, and ~6.5x in firefox). You can do this directly in elm.
The `_Utils_eq` function is very likely deoptimized because it can take _any_ two values and either do a reference check, or do structural equality, which we also know takes a while.
This hasn't shown any measureable benefit. Likely because this is such a simple function that all js compilers are already optimizing the intermedaite calls.
This was done for asset size. The nuance being that it's done to potentially optimize the _minified_ size of code, but not necessarily the gzipped version.
This is still a benefit because the minified code is what ultimately needs to be parsed and parsing is one of the larger steps on the way to getting a page running.
We weren't able to pin down a benchmark where this reported a benefit in the numbers, though likely to explore this we need (1) A larger codebase, and (2)
This transformation hasn't been attempted yet, but the idea is that if a constant is detected in a let statement, it can be declared moved to top-level instead of recalculated every function run.
This is risky! You do less computation, but you are (1) moving a bunch of computation to happen on start-up and (2) the results are allocated but can never be freed.
So, if two nodes were reference equal, you wouldn't have to ever diff them. I imagine this could be a big benefit if there was a long list where each element contained a somewhat large "constant" node for some UI thing.
In Elm, if you have a recursive function that calls itself at the top level, then it will be compiled into while loop. Here's an example:
```elm
sum : Int -> List Int -> Int
sum current list =
case list of
[] ->
current
(x :: remaining) ->
sum (x + current) remaining
```
The critical part here is that `sum` is called as the _first thing_ on that branch. Because that's easy to detect, we can reliably convert the above code into a `while` loop. (Note, all this stuff is called _Tail Call Optimization_).
However, it's a pretty common case where we can't quite do that.
Let's take a look at an implementation of `List.map`. (Note, this isn't how Elm currently does it, this is just for illustrative purposes.)
We could implement `List.map` like this:
```elm
type List a = Nil | Cons a (List a)
map : (a -> b) -> List a -> List b
map func list =
case list of
Nil -> Nil
Cons x xs ->
Cons (func x) (map func xs)
```
But our recursive function(`map`) is not the _first thing_ being called in that branch! It's the `Cons` constructor! (Heyy, Tail Recursion Modulo **Cons**)
The idea for this transformation is that we could take the above code, and generate the following JS code.