Outside of core, type variables are essential for any generalized data structures you might write as libraries. I don’t need to know or care what kind of data is associated with the nodes in a tree, for example, in order to code up all the tree navigation and manipulation operations that are needed to work with tree. Here is a tree data structure I wrote:
tea-tree src on github
Similar can be said for structuring computations which always follow the same pattern, but where some of the implementation details can be abstracted out, so that the same structure can be re-used in many different situations. Here are examples for working with nested updates, and for generalized searches over graphs:
elm-update-helper src on github
ai-search src on github
The above examples are all from packages where some code has been generalized for re-use in different situations. The type variables allow unknown behavior to abstracted out and combined with the known code in the package. This allows more sophisticated tools to be constructed from their parts without having to go back to beginning each time. In brief, it is one of the mechanisms through which ‘software engineering’ can be done.
To give an example from an application instead of a package, here are some type declarations from one of my applications:
type alias LinkBuilder msg =
String -> Html.Attribute msg
type alias Template msg =
LinkBuilder msg -> Editor msg -> Zipper Content -> Html msg
type alias Layout msg =
Template msg -> Template msg
type alias Editor msg =
Zipper Content -> Html msg
I have made use of both type variables and higher order functions here. Higher order functions (call-backs) are another excellent mechanism through which changeable program behavior can be combined together in a pluggable way; you implement some code, then use the function to inject variable behavior. Probably the most well-known example of this is List.map
. This is a loop (the known behavior) combined with a function (the unknown behavior), and the caller can inject whatever map function they like but never has to write a for (int i = 0; i < length; i++)
loop again.
To come back to my application - I have 2 types of Editor. One implementation is a content editor that lets me edit content as markup, and the other is not really an editor at all, it is just a markup renderer. So this particular application has 2 modes of operation, read mode and edit mode. In read mode msg
gets bound to Never
and in edit mode it gets bound to a Msg
type that captures all of the edit actions.
I could capture those 2 possibilities as a custom type with 2 options, but I would rather not. None of the code that works with Templates
and Layouts
needs to know anything about how the editors might work. Using type variables eliminates the possibility that this code might bind to things it should know nothing about. I also plan to add more types of editors to my application, perhaps one for adjusting images for example.
===
- What are some benefits of using type variables? What are some drawbacks?
A very important software engineering aspect of the language. Many things are simply not possible without them.
- What contexts most benefit from type variables? elm-core, library code, app code
Mostly core and library code, I agree. But I have also shown how they can be used in conjunction with higher order functions to structure more complex applications.
- What are examples of situations where you’ve found type variables improved your code?
Not just improved, but made things possible which are not possible otherwise.
- What are examples of situations where you regretted using type variables?
Sometimes I have experimented with structures like this:
type alias TextDiagram a =
{ a
| labels : List PathSpec
, pathsForLabels : EveryDict PathSpec Path
}
src on github
The idea being that the caller can embed the fields my package needs in some record in their application. A few other packages also did this kind of thing, elm-mdl
is one that comes to mind. It worked fine, but I think was an unnecessary complication and would have been better a different way.
- Do you have a personal guideline on when you use type variables?
As I say, whenever I need to abstract out some behavior to re-use code, and when building a scaffolding for a more complex application.