This Guide will teach you how to read and write function typing in nix comments.
This typing convention is followed by many nixpkgs functions including those in nixpgks/lib
🚧 🚧 Any help is welcome! 🚧 🚧
Example
( a -> Bool ) -> [ a ] -> Bool
--└────────┘ ↑ ↑
-- | | └── final return value is a Bool
-- | └── The function returns a function that takes a list of type 'a'
-- └── function that takes an 'a' and returns True or False (Boolean)
Tip
Prefer Type variables (a
, b
) over using the Any
keyword.
Tip
Prefer using more explicit {}
or []
syntax over vague List
or AttrSet
keywords.
Sometimes we don't care what List or Attribute-set someone passes.
Arbitrary Sets or Lists can be denoted with the ...
operator:
AttrSet of Any
{ ... }
-- ↑
-- └── Attribute Set; where the content is unknown / can be anything.
{ ${a} :: b }
-- ↑
-- └── Above is equivalent in meaning with this explicit expression.
-- └── `a` is a type variable. Is constraint to the `string` type, such that it can be used as an attribute key.
-- └── `b` is a type variable. If constraint in other places it has the `Any` type.
Note
...
never needs ;
(semicolon) because it is always the last entry.
List of Any
[ ... ]
-- ↑
-- └── List; where the content is unknown / can be anything.
AttrSet with dynamic names
{ ${name} :: Bool; }
-- ↑
-- └── name is of type [ String ]; This list of actual members is calculated at evaluation time. But we know every member has a value of type `Bool`
Warning
The following should be avoided. Consider the following interfaces bad - we're mixing different types of values in a single structure - It's like having a drawer with both socks and utensils; it's hard to know what you're grabbing without looking.
-- Invalid !
foo :: {
${name} :: Number;
${version} :: Derivation;
}
-- ↑
-- └── there are dynamic entries with different types.
-- The keys of `version` contain a Derivation.
-- The keys of `name` contain a Number.
-- This is ill advised, when accessing the above structure with `foo.bar` what type does it value have?
-- Therefore only one dynamic key can exist. Its type is the Union of all dynamic entries.
foo :: {
${nameOrVersion} :: Number | Derivation;
}
The above type is now demonstrated in a concrete value.
{
"foo" = 1;
"1.0.1" = derivation;
"bar" = 2;
"1.2.1" = derivation;
}
Any -> Any
-- becomes
a -> a
-- ↑
-- └── This is more concrete. And shows if values relate to each other
-- More
a -> b -> b
-- e.g. builtins.trace
(a -> b -> c) -> (b -> a -> c)
-- e.g. lib.flip
Attrs -> a
-- becomes
{ foo :: String, ${others} :: a }: -> a
-- ↑
-- └── This function takes an Attribute set. It expects to have a `foo` of type `String`
-- Any other entries are valid as well.
# Function with optional attrs
complicatedThing = {
foo # <- Required
bar ? 1 <- Optional
}:
#...
typeof complicatedThing
-- becomes
{
foo :: String
bar :: Int ?
}: -> a -- ↑
-- └── This attribute is optional. It may or may not exist on the passed attribute set.
Sometimes, writing a quick note about what type of information (like a number or text) a part of your code is expecting can be helpful. It's like leaving a little API hint for yourself and others. Coming back to a certain piece of code after a while you will be very thankful that you documented the APIs and don't need to rethink your entire program.
When you do this, you might notice things you didn't see before.
You might find out that using your code is harder than it should be. Sometimes it can help to realize why code isn't as flexible as intended. Writing down the types helps to step back and see the big picture, making it easier to spot and fix these issues
The full convention is available as mdbook here.