Type Variables
Abstract
As the explicit Any keyword is prohibited. Even there is no such Any type.
It is of course possible to construct the Any type and use it properly but in good type systems there is no need for that type.
The Any type is covered from Polymorphism with the usage of type variables.
Type variables can have any name but it is usually considered best practice to choose either letters from the latin alphabet or use meaningful names.
Type variablesmust be written in LOWERCASE to prevent confusion.
Examples
The following function signatures from nix and nixpkgs are written most clearly with type variables
builtins.map
( a -> b ) -> [a] -> [b]
builtins.catAttrs
String -> [ { ${name} :: a } ] -> [a]
lib.attrsets.attrByPath
[ String ] -> a -> { ${path} :: b } -> (a | b)
builtins.mapAttrs
(String -> a -> b) -> { ${name} :: a } -> { ${name} :: b }
builtins.sort
(a -> a -> Bool) -> [a] -> [a]
Namespaces
Type variables are currently the best way to properly use polymorphism in type signatures. But using them requires the underlying parser and the user need to understand some concepts like namespaces and shadowing.
This section orients itself on the Haskell type variables. See here for their rules
In nix we don't have the same syntax as haskell has but the basic principles and core concept will be adopted from the parser.
Bigger sets with nested type variables
sometime it is possible to have expressions, that contain multiple levels of nestings, where also different hierarchies of type variables must interact with each other.
{ foo :: a } -> {
nestedFunction :: a -> b,
bar :: ( b -> c ) -> [c] -> [b]
}
This seem a little complex now, because the parent function returns an AttrSet that includes function signatures using the same type variables as the outermost function itself.
There is actually a very strict and proven ruleset for that exact problem out there.
The Haskell Ruleset for scoped type variables
(see also above).
Type Variables with constraints
Sometime we have variable types in signatures, but cannot allow any type for the variable. Then we could construct a type and assign it like this.
Foo = ( String | Path )
Foo -> Foo
This is already allowed syntax in the proposed type system. There are no additional changes that need to be made.
Sometimes there might be more than one expression necessary to express the type of a function.
Also this means that the annotated function might take a String or a Path, and also returns them.
If that function takes a String it will return the same String type. Once Foo evaluates to type String every occurrence of Foo is then of type String.