/**
 * This module contains basic functionality for manipulating lists.
 *
 */
module collection/list/common
imports 
  collection/list/cons
  collection/list/index
  term/integer

strategies

  /**
   * Iterative loop over a list applying s to each element.
   *
   * @param Strategy to apply to every element (a -> _)
   * @type List(a) -> List(a)
   */
  list-loop(s) =
    ?xs; prim("SSL_list_loop", s | xs)

  /**
   * Iterative fold over a list applying s to each element and 
   * accumulator acc from left to right.
   *
   * @param Strategy applied for folding. The current
   *        intermediate result is a term argument. The next element
   *        of the list is the current term.
   * @param Initial value for folding (type: c)
   * @type List(a) -> c
   */
  list-fold(s : c * a -> c | acc) =
    ?xs; prim("SSL_list_fold", s | acc, xs)

strategies

  /** @internal */
  Hd   : [x | _] -> x
  
  /** @internal */
  Tl   : [_ | l] -> l
  
  /** @internal */
  Last : [x]     -> x

  /**
   * @internal
   * @type a -> List(a)
   */
  MkSingleton =
    ![<id>]

  /**
   * Splits a Cons into a tuple with head and tail.
   *
   * @type List(a) -> (a, List(a))
   */
  split-Cons :
    [x | xs] -> (x, xs)

  /**
   * Makes a Cons out of a tuple with head and tail. The
   * tail must be a list, but may be the empty list.
   *
   * @type (a, List(a)) -> List(a)
   */
  MkCons :
    (x, xs) -> [x | xs]

strategies

  /**
   * Succeeds if the input term is a list.
   *
   * @type List(a) -> _
   */
  is-list = ?[] + ?[_ | _]

  /**
   * Apply a strategy to each element of a list.
   *
   * @param s is applied to all elements: a -> b
   * @type  List(a) -> List(b)
   * @inc   map-test
   */
  map(s) = 
    rec x([] + [s | x]) 

  /** @internal */
  map1(s) = 
    [s | id]; [id | try(map1(s))] <+ [id | map1(s)]

  /**
   * In reverse order, apply a strategy to each element of a list.
   *
   * @param s is applied to all elements: a -> b
   * @type  List(a) -> List(b)
   */
  reverse-map(s) = 
    [id | reverse-map(s)]; [s | id] <+ []

  /**
   * @inc map
   *
   * @note list(s) is an alias for map
   */
  list(s) = 
    map(s)

  /** 
   * Apply a strategy to some elements in a list. The elements
   * of the original list will be kept unchanged when the 
   * strategy s fails.
   *
   * @param s       a -> b
   * @type    List(a) -> List(b)
   */
  list-some(s) =
    rec x([s| id] < [id| list(try(s))] + [id| x])

 /**
  * Returns the length of a list.
  *
  * @type List(a) -> Int
  * @inc length-test
  */
  length = 
    ?t; prim("SSL_get_list_length", t)

  /**
   * Succeeds if the term is in the list.
   *
   * @type  (a, List(a)) ->? List(a)
   */
  elem =
    ?(x, <id>); fetch(?x)
  
  /**
   * Succeeds if the term is in the list, using the given
   * strategy for determining equality.
   *
   * @param (a, a) ->? b
   * @type  (a, List(a)) ->? List(a or b)
   */
  elem(eq) =
    ?(x, <id>); fetch(<eq> (x, <id>))

  /**
   * Find first list element for which s succeeds.
   *
   * @param a -> b
   * @type  List(a) -> List(a or b)
   * @inc   fetch-test
   */
  fetch(s) = 
    rec x([s | id] <+ [id | x])

  /**
   * Return first list element for which s succeeds.
   *
   * @type  List(a) -> a
   * @inc   fetch-elem-test
   */
  fetch-elem(s) = 
    fetch(s; ?x); !x

  /**
   * Splits a list in two parts at the first point where s succeeds.
   *
   * The element to which s was applied is not part of the result. split-fetch
   * fails if s cannot be applied to any of the elements.
   *
   * Examples:
   *   <split-fetch(\ 3 -> 6 \)> [1, 2, 3] => ([1,2], [])
   *   <split-fetch(\ 3 -> 6 \)> [2, 3, 4] => ([2], [4])
   *   <split-fetch(\ 3 -> 6 \)> [3, 4, 5] => ([], [4,5])
   *   not(<split-fetch(\ 3 -> 6 \)> [8, 7, 6])
   *
   * @param a ->? _
   * @type  List(a) -> (List(a), List(a))
   * @inc   split-fetch-test
   */
  split-fetch(s) =
    at-suffix([s|id];[id|?tl];![]); !(<id>, tl)

  /**
   * Splits a list in two parts at the point where s succeeds, keeping the
   * element at which s succeeded.
   *
   * Unlike split-fetch, this strategy keeps the dividing element as part of
   * the result as the second element in the triple: (before, split, after)
   *
   * split-fetch-keep fails if s cannot be applied to any of the elements.
   *
   * @param a -> b
   * @type  List(a) -> (List(a), b, List(a))
   */
  split-fetch-keep(s) =
    at-suffix([s|id];[?el|?tl];![]); !(<id>, el, tl)
    
  /**
   * Breaks a list into multiple parts (tokens). 
   *
   * The term argument sep is a list of delimiters (elements that separate
   * tokens from one another), which is used to split the list
   * into multiple tokens. The result is a list of lists, i.e. a list
   * of tokens.
   *  
   * @param List of separator elements.
   * @type  List(a) -> List(List(a))
   */
  list-tokenize(|sep) =
    list-tokenize({c: ?c; <fetch(?c)> sep})

  /**
   * Breaks a list into multiple parts (tokens). 
   *
   * The strategy argument sep is used to split the list. Wherever it
   * succeeds, the original list is split, and the parts are returned
   * as a list of lists, i.e. a list of tokens.
   * 
   * @param s  a -> fail?
   * @type     List(a) -> List(List(a))
   */
  list-tokenize(sep) =
      (split-fetch(sep) <+ !(<id>, []))
    ; ( \ (  []     , [])        -> []   \
      + \ (l@[_ | _], [])        -> [l]  \
      + \ (  []     , l@[_ | _]) -> <list-tokenize(sep)> l \
      + \ (t@[_ | _], l@[_ | _]) -> [t | <list-tokenize(sep)> l] \
      )

strategies    

  /**
   * Apply a strategy to the tail of a list.
   *
   * @param is applied to the tail: List(a) -> List(b)
   * @type  List(a) -> List(a or b)
   */
  at-tail(s) = 
    [id | s]

  /**
   * Apply s to the Nil of a list. 
   *
   * @param is applied to Nil ([]) and must return a list: List(a) -> List(a)
   * @type  List(a) -> List(a)
   */
  at-end(s) = 
    rec x([id | x] + []; s)

  /**
   * Apply a strategy to some suffix of a list.
   *
   * The longest suffix (that is, the first application in a list)
   * is preferred.
   *
   * @param is applied to the suffix : List(a) -> b
   * @type  List(a) -> List(a or b)
   */
  at-suffix(s) = 
    rec x(s <+ [id | x])

  /**
   * Apply a strategy to some suffix of a list.
   *
   * The shortest suffix (that is, the last application in a list)
   * is preferred.
   *
   * @param is applied to the suffix : List(a) -> b
   * @type  List(a) -> List(a or b)
   */
  at-suffix-rev(s) = 
    rec x([id | x] <+ s)

  /**
   * Apply s to the last Cons ([_]) of a list.
   *
   * @param Is applied to the [x] and must return a list: List(a) -> List(a)
   * @type  List(a) -> List(a)
   */
  at-last(s) = 
    rec x([id]; s <+ [id | x])

  /**
   * Splits a list into a tuple of its init list and last element.
   *
   * Example:
   *   <split-init-last> [1, 2, 3, 4] => ([1, 2, 3], 4)
   *   <split-init-last> [1]          => ([], 1)
   *
   * @type   List(a) -> (List(a), a)
   * @inc    split-init-last
   * @since  0.9.4
   */
  split-init-last = 
    at-last(?[x]; ![]); !(<id>, x)

  /**
   * Applies s1 to all elements in a list, except the last, where
   * s2 is applied.
   *
   * @param s1 a -> b 
   * @param s2 a -> b
   * @type List(a) -> List(b)
   */
  at-init(s1, s2) =
    rec x([s2] <+ [s1 | x])

  /**
   * Applies a strategy to a list in bottom up order. That is to say,
   * the strategy s will be applied to successively longer excerpts
   * of the list, starting from the end. 
   *
   * At the first invocation, s will be applied to the tail of the list,
   * and is expected to return a new list. The last element of the list
   * will then be added in front of this result, and s is applied to
   * this. The recursion continues through all elements from last to
   * first, each time on a longer list, hence "bottom up".
   *
   * Example: <listbu(![9 | <id>])> [1,2,3,4] => [9,1,9,2,9,3,9,4,9]
   *
   * @param s  List(a) -> List(b)
   * @type     List(a) -> List(b)
   */
  listbu(s) = 
    rec x(([] + [id| x]); s)

  /**
   * @inc listbu
   */
  listbu1(s) = 
    [id| listbu1(s)]; try(s) <+ s

  /**
   * Applies a strategy to a list in top down order. That is to say,
   * the strategy s will first be applied to the whole list, then 
   * successively shorter excerpts, all the way chopping of elements from
   * the start of the list.
   *
   * At the first invocation, s will be applied to the whole list, and
   * is expected to return a new list. The first element is chopped off
   * this result, and s is applied again, until s has been applied to
   * the empty list. The recursion continues through successively 
   * shorter list, hence "top down".
   *
   * @note The strategy s cannot result in a list which is longer than
   * it is given, because that will result in non-termination.
   *
   * Example: <listtd(not(?[]) ; ![<sum>] <+ ![])> [1,2,3,4] => [10]
   *
   * @param s  List(a) -> List(b)
   * @type     List(a) -> List(b)
   */
  listtd(s) = 
    rec x(s; ([] + [id| x]))

  /**
   * Applies s in a top down then bottom up, i.e. down up, order. See
   * listtd and listbu for a detailed description of each phase.
   *
   * @note As with listtd, the strategy s can never result in a list
   * which is longer than given to it.
   *   
   * @param s List(a) -> List(b)
   * @type    List(a) -> List(b)
   */
  listdu(s) =
    rec x(s; ([] + [id| x]); s)

  /**
   * Applies s1 in a top down order then s2 in a bottom up order. See
   * listd and listbu for a detailed description of each phase.
   *
   * @note As with listtd, the strategy s2 can never result in a list
   * which is longer than given to it.
   *
   * @param s1 List(a) -> List(b)
   * @param s2 List(a) -> List(b)
   * @type     List(a) -> List(b)
   */
  listdu2(s1, s2) = 
    rec x(s1; ([] + [id| x]); s2)

  /** @internal */
  RevInit : xs -> (xs, [])
  
  /** @internal */
  Rev     : ([x| xs], ys) -> (xs, [x| ys])
  
  /** @internal */
  RevExit : ([], ys) -> ys

  /**
   * Reverses a list.
   *
   * @inc reverse-test
   * @type List(a) -> List(a)
   */
  reverse = 
    reverse-acc(id, ![])

  /**
   * Reverses a list and applies s to all the elements.
   *
   * @param a -> b
   * @type  List(a) -> List(b)
   */
  reverse(s) = 
    reverse-acc(s, ![])

  /** @internal */
  reverse-acc(s, acc) : 
    [] -> <acc>()

  /** @internal */
  reverse-acc(s, acc) : 
    [x | xs] -> <{ys:where(![<s>x | <acc>] => ys); reverse-acc(s, !ys)}> xs

rules

  /** @internal */
  UptoInit : i -> (i, [])
  
  /** @internal */
  UptoExit : (i, xs) -> xs where <lt> (i, 0)
  
  /** @internal */
  UptoStep : (i, xs) -> (<subt> (i, 1), [i| xs])

strategies

  /**
   * Generates a sequence of numbers from 0 up to the given input
   * integer, inclusive. 
   *
   * See also range.
   *
   * @type Int -> List(Int)
   *
   * @inc upto-test
   */
  upto =
    UptoInit; rec x(UptoExit <+ UptoStep; x)

strategies

  /**
   * Concatenates all lists of a tuple.
   *
   * @type (List(a), List(a), ...) -> List(a)
   * @inc  conc-test
   */
  conc =
    \ (l1, l2) -> <at-end(!l2)> l1 \
    <+ \ "" # (xs) -> <concat> xs \

  /**
   * Concatenates a list of lists into one list.
   *
   * Example: <concat> [[1,2],[3,4],[5,6]] => [1,2,3,4,5,6]
   *
   * @type List(List(a)) -> List(a)
   * @inc  concat-test
   */
  concat =
    rec x([] + \ [l | ls] -> <at-end(<x> ls)> l\ )

  /**
   * Concats two elements if both elements are lists. Otherwise, constructs
   * a Conc term.
   *
   * Generic term construction is used to avoid infinite recursion: makeConc
   * is used in the compilation of Conc itself.
   *
   * @type List(a) * List(b) -> List(a|b)
   * @type a * b -> Conc(a,b)
   */
  makeConc = 
    ?(xs, ys)
    ; if <is-list> xs; <is-list> ys then conc else !"Conc"#([xs, ys]) end

strategies

  /**
   * Separates the elements of the list by the specified separator.
   * The separate-by variant that uses a term argument is prefered.
   *
   * @type (sep, List(a)) -> List(a or sep)
   */
  separate-by =
    ?(sep, <id>)
    ; separate-by(|sep)

  /**
   * Separates the elements of the list by the specified separator.
   * The separate-by variant that uses a term argument is prefered.
   *
   * @param Strategy that results in a separator.
   * @type  List(a) -> List(a or sep)
   */
  separate-by(sep) =
    separate-by(|<sep> ())

  /**
   * Separates the elements of the list by the specified separator.
   *
   * @param Separator term
   * @type  List(a) -> List(a or sep)
   */
  separate-by(|sep) =
    []
    + [id |
        rec x(
          []
        + [id | x]
          ; ![sep | <id>]
        )]

strategies

  /**
   * Transposes an n by m matrix. The matrix must be represented as
   * a list of n elements, where each element is a list of length m.
   * The element of the inner lists may be of any type.
   *
   * Example: <matrix-transpose> [[1,2],[3,4]] => [[1,3],[2,4]]
   *
   * @type List(List(a)) -> List(List(a))
   */
  matrix-transpose =
      map(?[]); ![]
    +   map(split-Cons)
      ; unzip
      ; (id, matrix-transpose)
      ; MkCons

  /**
   * <for-each-pair(s)> (xs, ys) produces the list of pairs <s> (x,y).
   * for each pair of x from xs and y from ys.
   *
   * @inc for-each-pair-test
   *
   * @type List(a) * List(b) -> List((a,b), ...)
   */
  for-each-pair(s) =
    ?(xs, ys); <map(\ x -> <map(<s>(x,<id>))> ys \ )> xs

strategies

  /**
   * Succeeds if the first input term is a member of the second.
   *
   * @type a * List(a) -> a
   */
  member = (?x, fetch(?x))

rules

  /** @internal */
  FoldR1   : [x, y] -> (x, y)

  /** @internal */
  FoldR    : [x | xs] -> (x, xs)

  /** @internal */
  FoldL(s) : ([x | xs], y) -> (xs, <s> (x, y))

  /** @internal */
  lsplit(f, g) : x -> [<f> x, <g> x]

strategies

  /**
   * foldr, requires a list of length > 1.
   *
   * @param  List(a) -> b
   * @param  (a, b) -> b
   * @type   List(a) -> b
   * @internal
   */
  foldr1(s1, s2) = 
    rec x([id]; s1 <+ FoldR; (id, x); s2)

  /**
   * foldr, requires a list of length > 1.
   * The additional parameter strategy f is applied to each element just
   * before each folding step.
   *
   * @param List(c) -> b
   * @param (c, b) -> b
   * @param a -> c
   * @type  List(a) -> b
   * @internal
   */
  foldr1(s1, s2, f) = 
    rec x([f]; s1 <+ FoldR; (f, x); s2)

  /**
   * foldr, requires a list of length > 1.
   * Note that s maps (a, a) to a, only one type is involved.
   *
   * @param (a, a) -> a
   * @type  List(a) -> a
   * @internal
   */
  foldr1(s) = 
    rec x((FoldR1 <+ FoldR; (id, x)); s)

  /**
   * Right folds a list. That is, the strategy s2 is applied as a 
   * binary operator between all adjacent elements in the list.
   * foldr starts by applying s2 to the last element in the list
   * and the result of s1. s1 is therefore the starting point of
   * the folding.
   *
   * Example: <foldr(!0, add)> [1,2,3,4] => 10
   *
   * @param [] -> b
   * @param (a, b) -> b
   * @type  List(a) -> b
   */
  foldr(s1, s2) = 
    []; s1 
    + \ [y|ys] -> <s2>(y, <foldr(s1, s2)> ys) \

  /**
   * Right folds a list. That is, the strategy s2 is applied as a 
   * binary operator between all adjacent elements in the list.
   * foldr starts by applying s2 to the last element in the list
   * and the result of s1. s1 is therefore the starting point of
   * the folding.
   *
   * The additional parameter strategy f is applied to each element just
   * before each folding step.
   *
   * Example: <foldr(!0, add, inc)> [1,2,3,4] => 14
   *
   * @param [] -> b
   * @param (c, b) -> b
   * @param a -> c
   * @type  List(a) -> b
   */
  foldr(s1, s2, f)  = 
    []; s1 + 
    \ [y|ys] -> <s2> (<f> y, <foldr(s1, s2, f)> ys) \

  /**
   * Left folds a list. That is, the strategy sis applied as a 
   * binary operator between all adjacent elements in the list.
   * foldr starts by applying s to b and the first element in
   * the list. b is therefore the starting point of the folding.
   *
   * Example: <foldl(add)> ([1,2,3,4], 0)
   *
   * @param (a, b) -> b
   * @type  (List(a), b) -> b
   */ 
  foldl(s) = 
    rec x( \ ([], y) -> y \ + FoldL(s); x)

  /** @internal */
  mapfoldr1(s1, s2, s3) = 
    rec x([id]; s1 <+ [s2|x]; \ [a|b]->(a,b)\; s3)

  /** 
   * Transform the elements of a list into lists (map)
   * and concatenate into a single list (concat).
   * 
   * Note: equivalent to map(s); concat
   *
   * @param a -> List(b) 
   * @type  List(a) -> List(b)
   */
  mapconcat(s) =
    foldr([], conc, s)

  /**
   * Returns the last element of a list.
   *
   * Fails if applied to the empty list.
   *
   * @type List(a) -> a
   */
  last = 
    rec x(Last <+ Tl; x)


  /**
   * Returns a list with the first and the last element of
   * the input list. For the empty list and the singleton
   * list, this is equivalent to id.
   *
   * @type List(a) -> List(a)
   */
  first-last =
    [id | try(last; MkSingleton)] <+ []

 /**
  * Returns a list of all elements of a list, except the last.
  * 
  * Fails if applied to the empty list.
  *
  * @inc init
  * @inc empty init
  *
  * @type List(a) -> List(a)
  */
  init = 
    at-last(Tl)

  /**
   * @inc split-init-last
   * @note Alias for split-init-last.
   */ 
  split-last =
    split-init-last

  /**
   * Makes n copies of a term into a list of duplicates. The
   * first input term is the integer n, the second is the term
   * to duplicate.
   *
   * @inc copy-test
   *
   * Example: <copy> (3, "foo") => ["foo", "foo", "foo"]
   *
   * @type Int * a -> List(a)
   */
  copy = 
    for(\ (n,t) -> (n,t,[]) \
       ,\ (0,t,ts) -> ts \
       ,\ (n,t,ts) -> (<subt>(n,1), t, [t|ts]) where <geq>(n,1) \ )

  /**
   * Makes n copies of a term into a list of duplicates, applying
   * the strategy s to every copy. The first input term is the 
   * integer n, the second is the term to duplicate.
   * 
   * Example: <copy(\ "foo" -> "bar" \)> (3, "foo") => ["bar","bar,"bar"]
   *
   * @param s       a -> b
   * @type    Int * a -> List(n)
   */
  copy(s) = 
    for(\ (n,t) -> (n,t,[]) \
       ,\ (0,t,ts) -> ts \
       ,\ (n,t,ts) -> (<subt>(n,1), t, [<s> t|ts]) where <geq>(n,1) \ )

  /** @internal */
  thread-map(s) :
    ([], t) -> ([], t)

  /**
   * Applies s to each element in the list, keeping along a separate
   * context term. 
   *
   * For each element in the list, a tuple (a, b) is constructed and
   * given to s. From the result, (a', b'), a' goes to the final list
   * returned by this strategy, and b' becomes the new b as s is
   * applied to the next element.
   *
   * Example: <thread-map(add ; !(<id>, <id>))> ([1,2,3,4], 1) => ([2,4,7,11],11)
   *
   * @param s a * b -> a' * b'
   * @type List(a) * b -> List(a') * b'
   */ 
  thread-map(s) :
    ([x | xs], t) -> ([y | ys], t'')
    where <s> (x, t) => (y, t')
	; <thread-map(s)> (xs, t') => (ys, t'')

  /**
   * Numbers each element in a list successively with an
   * integer, starting at 0. The result is a list of pairs,
   * (elem, num) where elem is the original element and num
   * is its associated number. s is applied to each pair
   * before inserting it into the list
   *
   * Example: <number(id)> ["a","b","c"] => [("a",0),("b",1),("c",2)]
   *
   * @inc number-test
   *
   * @param s a * Int -> a' * Int
   * @type List(a) -> List((a,n),...)
   */
  number(s) =
    !(<id>, 0); thread-map(!(<s>,<Snd;inc>)); ?(<id>,_)

  /**
   * Take elements from the start of a list while s succeeds.
   * Each element of the list is tested against s, starting at
   * the head of the list. For as long as s succeeds, the elements
   * are accumulated in a list, which is returned as s fails, or
   * the end of the list is reached. The actual term returned by
   * s is ignored.
   *
   * Example: <take-while(?2 ; !3)> [2,2,3] => [2,2]
   *
   * @param s       a -> _
   * @type    List(a) -> List(a)
   */
  take-while(s) = 
    at-suffix([] + ([not(s)|id];![]))

  /**
   * Take elements from the start of a list until s succeeds. 
   * Each element of the list is tested against s, starting at
   * the head of the list. For as long as s does not succeed, the 
   * elements are accumulated in a list, which is returned at
   * the instant s fails. The actual term returned by s is
   * ignored. If s never succeeds, the entire list is returned.
   *
   * Example: <take-until(?2; !3)> [3,3,2,4] => [3,3]
   *
   * @param s       a -> _
   * @type    List(a) -> List(a)
   */
  take-until(s) = 
    at-suffix([] + ([s|id];![]))

  /**
   * Take the first n elements of a list, given by isn. The
   * strategy argument isn must produce an integer, which
   * gives the length of the sublist to return. If there are
   * not enough elements, this strategy fails.
   *
   * @param isn _ -> Int
   * @type List(a) -> List(a)
   */
  take(isn) = 
    take(|<isn>)

  /**
   * Returns the first n elements of a list, fails
   * if list has fewer than n elements.
   *
   * @param n The number of elements to take.
   * @type List(a) -> List(a)
   */
  take(|n) =
    if <eq>(n,0) then 
      ![]
    else 
      ![<Hd> | <Tl; take(|<subt>(n,1))>]
    end

  /** 
   * Returns the n first elements after s has been applied to them.
   * With the exception of side effects, takemap(s|n) is equal to
   * take(|n); map(s). The difference when considering side-effects
   * is that s is applied while taking elements, so if s has a
   * side-effect these will be performed, even if take fails.
   *
   * @param n - The number of elements to retrieve
   * @param s a -> b 
   * @type List(a) -> List(b)
   */
  takemap(s|n) =
    if <eq>(n,0) then 
      ![]
    else 
      ![<Hd; s> | <Tl; takemap(s|<subt>(n,1))>]
    end
  
  /**
   * Drops elements from the start of a list while s succeeds. The
   * first element at which s fails and all following it will be
   * returned.
   *
   * Example: <drop-while(?2)> [2,2,3,4] => [3,4]
   *
   * @param s       a -> _
   * @type    List(a) -> List(a)
   */
  drop-while(s) = 
    at-suffix(([] + [not(s)|id]);?xs); !xs

  /**
   * Drops elements from the start of a list until s succeeds. The
   * first element at which s succeeds and all following it will be
   * returned.
   *
   * Example: <drop-until(?3)> [2,2,3,4] => [3,4]
   *
   * @param s       a -> _
   * @type    List(a) -> List(a)
   */
  drop-until(s) = 
    at-suffix(([] + [s|id]);?xs); !xs

  /**
   * Drops the first n elements from a list. If the list has
   * fewer than n elements, the strategy fails.
   *
   * @param n - the number of elements to drop
   * @type  List(a) -> List(a)
   */
  drop(|n) =
    if <eq>(n,0) then 
      id
    else 
      Tl; drop(|<subt>(n,1))
    end

  /** 
   * Splits a list after n elements and applies strategy s to the
   * first sublist. The second sublist is left untouched. 
   * Disregarding side-effects, splitmap is equal to 
   * !(<take(|n); map(s)>, <drop(|n)>). If side-effects are
   * considered, note that application of s happens while
   * traversing and splitting.
   *
   * @param s a -> b
   * @param n - the number of elements to apply s to, from the start
   * @type List(a) -> (List(b), List(a))
   */
  splitmap(s|n) =
    if <eq>(n,0) then 
      !([], <id>)
    else 
      where(Hd; s => x)
      ; where(Tl; splitmap(s|<subt>(n,1)) => (xs, ys))
      ; !([x | xs], ys)
    end

  /**
   * @inc split-fetch
   * @note Alias for split-fetch/1
   */
  split-at(s) = 
    split-fetch(s)

  /**
   * Splits a list in two, with the second part containing the last
   * n elements and and first part containing all elements except the
   * last n.
   *
   * @type List(a) -> (List(a), List(a))
   * @param n - the number of elements to split at (counting from the back)
   */
  back-split-at(|n) =
    foldr(!(([], []), 0)
          , {x, l, r, m :
             ?(x, ((l, r), m))
             ; if <lt>(m, n)
               then
                 !((l, [x | r]), <inc>m)
               else
                 !(([x | l], r), m)
               end})
    ; ?(<id>, _)

  /**
   * Drops a number of terms from the front of a list.
   *
   * The number is specified by the strategy argument, which should
   * produce an integer.
   *
   * @type  List(a) -> List(a)
   * @param _ -> Int
   */
  drop(isn) = 
    where(isn => n)
    ; nzip0(id)
    ; drop-until(?(n,_))
    ; map(Snd)

  /**
   * Splits the list in two sublists, containing elements from 0 to
   * n and from n onwards.
   *
   * Example: <split-at(|4)>[1,2,3,4,5,6,7,8,9] => ([1,2,3,4], [5,6,7,8,9])
   *
   * @type List(a) -> (List(a), List(a))
   */
  split-at(|n) =
    at-index-tail(?tail; ![] | n)
    ; !(<id>, tail)

strategies

  /**
   * Trim elements from the end of a list
   *
   * Removes the longest sublist from the end of a list, for which
   * all elements satisfy the strategy s.
   *
   * @type  List(a) -> List(a)
   * @param should succeed for all elements that have to be trimmed.
   * @since 0.9.5
   * @inc   trim-test
   */
  rtrim(s) =
    ![()|<id>] // Add dummy element, or at-suffix-rev will fail at empty lists
  ; at-suffix-rev(
      where( not(?[])     // This only succeeds if we're not at list-end
           ; not([s|id])) // and s fails at the head of the current suffix
    ; ![<Hd>]) // s failed, no further trimming.
  ; Tl // Strip off dummy head element.

  /**
   * Trim elements from the start of a list.
   *
   * Removes the longest sublist from the start of a list, for which
   * all elements satisfy the strategy s.
   *
   * @type  List(a) -> List(a)
   * @param should succeed for all elements that have to be trimmed.
   * @since 0.9.5
   * @inc   trim-test
   * @note Alias for drop-while
   */
  ltrim(s) = drop-while(s)

  /**
   * Trim elements from both start and end of a list.
   * 
   * Removest the longest sublist from both start and end of a
   * list for which all elements satisfy s.
   *
   * @type List(a) -> List(a)
   * @param s a -> - 
   * @since 0.9.5
   * @inc trim-test
   */
  trim(s) = ltrim(s); rtrim(s)

strategies

  /** 
   * Completely flattens a list and its sublists to a single list.
   *
   * See list-misc-test for examples.
   * 
   * @type List(rec x(a or List(x))) -> List(a)
   * @inc flatten-test
   */
  flatten-list =
    foldr(![], (is-list, id) < conc + MkCons, is-list < flatten-list + id)


  /**
   * Eliminates all elements at the end of the two lists that are equal.
   * Only works correctly on lists of equal length!
   *
   * Example: <eliminate-common-suffix>([1,3,4], [1,2,4]) => ([1,3], [1,2])
   *
   * @type (List(a), List(a)) -> (List(a), List(a))
   */
  eliminate-common-suffix =
    ?([x | xs], [y | ys])
    ; <eliminate-common-suffix>(xs, ys)
    ; if ?([], []); <eq>(x, y)
      then !([], [])
      else (![x | <id>], ![y | <id>])
      end
    <+ !([], [])

  /**
   * Returns the common prefix of two lists.
   *
   * Examples:
   *  <common-prefix>([1,2,3], [1,2,4,5]) => [1,2]
   *  <common-prefix>([1,2,3], [2,3,4]) => []
   *
   * @type (List(a), List(a)) -> List(a)
   */
  common-prefix =
    ?([x | xs], [x | ys])
    ; ![x | <common-prefix>(xs, ys)]
    <+ ![]


strategies

strategies

  /**
   * Returns a list of combinations by choosing one element from every 
   * list, in every possible combination.
   * 
   * Examples:
   *
   *   $ <list-combinations> [[1, 2]]
   *   [[1],[2]]
   *
   *   $ <list-combinations> [[1, 2], []]
   *   []
   *
   *   $ <list-combinations> [[1, 2], ["a", "b"]]
   *   [[1,"a"],[2,"a"],[1,"b"],[2,"b"]]
   *
   *   $ <list-combinations> []
   *   [[]]
   *  
   * @type List(List(a)) -> List(List(a))
   */
  list-combinations =
    let step = fail
          <+ \ [] -> [[]] \

          <+ \ [[] | _] -> [] \

          <+ {xs :
               ?[xs | <step>]
               ; map-intermediate(|xs)
             }

        map-intermediate(|xs) = fail
          <+ \ [] -> [] \

          <+ {intermediate, tail, tail' :
               ?[intermediate | tail]
               ; tail' :=  <map-intermediate(|xs)> tail
               ; <map-xs(|intermediate, tail')> xs
             }

        /**
         * This is just a foldr, but Stratego does not support 
         * specialization of map-xs to an intermediate without 
         * introducing tuples, which means that we cannot
         * use a generic fold.
         */
        map-xs(|intermediate, tail) = fail
          <+ \ [] -> tail \

          <+ {x:
               ?[x | <id>]
               ; map-xs(|intermediate, tail)
               ; ![[x | intermediate] | <id>]
             }
    in step 
   end