with builtins;

rec {

  fold =
    op: nul: list:
    if list == [ ] then nul else op (head list) (fold op nul (tail list));

  concat = fold (x: y: x + y) "";

  and = fold (x: y: x && y) true;

  flatten = x: if isList x then fold (x: y: (flatten x) ++ y) [ ] x else [ x ];

  sum = foldl' (x: y: add x y) 0;

  hasSuffix =
    ext: fileName:
    let
      lenFileName = stringLength fileName;
      lenExt = stringLength ext;
    in
    !(lessThan lenFileName lenExt) && substring (sub lenFileName lenExt) lenFileName fileName == ext;

  # Split a list at the given position.
  splitAt =
    pos: list:
    if pos == 0 then
      {
        first = [ ];
        second = list;
      }
    else if list == [ ] then
      {
        first = [ ];
        second = [ ];
      }
    else
      let
        res = splitAt (sub pos 1) (tail list);
      in
      {
        first = [ (head list) ] ++ res.first;
        second = res.second;
      };

  # Stable merge sort.
  sortBy =
    comp: list:
    if lessThan 1 (length list) then
      let
        split = splitAt (div (length list) 2) list;
        first = sortBy comp split.first;
        second = sortBy comp split.second;
      in
      mergeLists comp first second
    else
      list;

  mergeLists =
    comp: list1: list2:
    if list1 == [ ] then
      list2
    else if list2 == [ ] then
      list1
    else if comp (head list2) (head list1) then
      [ (head list2) ] ++ mergeLists comp list1 (tail list2)
    else
      [ (head list1) ] ++ mergeLists comp (tail list1) list2;

  id = x: x;

  const = x: y: x;

  range = first: last: if first > last then [ ] else genList (n: first + n) (last - first + 1);

}
