lib: {
  lists = {
    from = {
      ## Convert a value to a list. If the value is already a list,
      ## it will be returned as-is. If the value is not a list, it
      ## will be wrapped in a list.
      ##
      ## @type a | (List a) -> List a
      any = value:
        if builtins.isList value
        then value
        else [value];
    };

    sort = {
      ## Perform a natural sort on a list of strings.
      ##
      ## @type List String -> List String
      natural = list: let
        vectorize = string: let
          serialize = part:
            if builtins.isList part
            then lib.strings.into.int (builtins.head part)
            else part;
          parts = lib.strings.split "(0|[1-9][0-9]*)" string;
        in
          builtins.map serialize parts;
        prepared = builtins.map (value: [(vectorize value) value]) list;
        isLess = a: b: (lib.lists.compare lib.numbers.compare (builtins.head a) (builtins.head b)) < 0;
      in
        builtins.map (x: builtins.elemAt x 1) (builtins.sort isLess prepared);
    };

    ## Map a list using both the index and value of each item. The
    ## index starts at 0.
    ##
    ## @type (Int -> a -> b) -> List a -> List b
    mapWithIndex = f: list:
      builtins.genList
      (i: f i (builtins.elemAt list i))
      (builtins.length list);

    ## Map a list using both the index and value of each item. The
    ## index starts at 1.
    ##
    ## @type (Int -> a -> b) -> List a -> List b
    mapWithIndex1 = f: list:
      builtins.genList
      (i: f (i + 1) (builtins.elemAt list i))
      (builtins.length list);

    ## Compare two lists using a custom compare function. The compare
    ## function is called for each element in the lists that need to
    ## be compared.
    ##
    ## @type (a -> b -> -1 | 0 | 1) -> List a -> List b -> Int
    compare = compare: a: b: let
      result = compare (builtins.head a) (builtins.head b);
    in
      if a == []
      then
        if b == []
        then 0
        else -1
      else if b == []
      then 1
      else if result == 0
      then lib.lists.compare compare (builtins.tail a) (builtins.tail b)
      else result;

    ## Get the last element of a list.
    ##
    ## @type List a -> a
    last = list:
      assert lib.errors.trace (list != []) "List cannot be empty";
        builtins.elemAt list (builtins.length list - 1);

    ## Slice part of a list to create a new list.
    ##
    ## @type Int -> Int -> List -> List
    slice = start: count: list: let
      listLength = builtins.length list;
      resultLength =
        if start >= listLength
        then 0
        else if start + count > listLength
        then listLength - start
        else count;
    in
      builtins.genList
      (i: builtins.elemAt list (start + i))
      resultLength;

    ## Take the first n elements of a list.
    ##
    ## @type Int -> List -> List
    take = lib.lists.slice 0;

    ## Drop the first n elements of a list.
    ##
    ## @type Int -> List -> List
    drop = count: list: let
      listLength = builtins.length list;
    in
      lib.lists.slice count listLength list;

    ## Reverse a list.
    ##
    ## @type List -> List
    reverse = list: let
      length = builtins.length list;
      create = i: builtins.elemAt list (length - i - 1);
    in
      builtins.genList create length;

    ## Interleave a list with a separator.
    ##
    ## @type Separator -> List -> List
    intersperse = separator: list: let
      length = builtins.length list;
    in
      if length < 2
      then list
      else
        builtins.tail (
          builtins.concatMap
          (part: [separator part])
          list
        );

    ## Create a list of integers from a starting number to an ending
    ## number. This *includes* the ending number as well.
    ##
    ## @type Int -> Int -> List
    range = start: end:
      if start > end
      then []
      else builtins.genList (i: start + i) (end - start + 1);

    ## Depending on a given condition, either use the given value (as
    ## a list) or an empty list.
    ##
    ## @type Attrs a b => Bool -> a -> a | b
    when = condition: value:
      if condition
      then
        if builtins.isList value
        then value
        else [value]
      else [];

    ## Count the number of items in a list that satisfy a given predicate.
    ##
    ## @type (a -> Bool) -> List a -> Int
    count = predicate: list:
      builtins.foldl' (
        total: value:
          if predicate value
          then total + 1
          else total
      )
      0
      list;

    ## Remove duplicate items from a list.
    ##
    ## @type List -> List
    unique = list: let
      filter = result: value:
        if builtins.elem value result
        then result
        else result ++ [value];
    in
      builtins.foldl' filter [] list;
  };
}