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 variables must 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.