Fuzion Logo
fuzion-lang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

Sequence.fz


# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation.  If not, see <https://www.gnu.org/licenses/>.


# -----------------------------------------------------------------------
#
#  Tokiwa Software GmbH, Germany
#
#  Source code of Fuzion standard library feature Sequence
#
#  Author: Fridtjof Siebert (siebert@tokiwa.software)
#
# -----------------------------------------------------------------------

# Sequence -- ancestor for features that can be converted to a 'list' or a
# 'stream'
#
# Sequences are empty, finite or infinite ordered collections of instances
# of a given type.  Sequences may calculate elements lazily on demand.
#
# Sequence is a 'ref' type, i.e., different sub-features may be assigned
# to a field of type 'Sequence'.
#
# Heirs of Sequence must implement either 'as_list' or 'as_stream'.  Failure
# to implement any of these results in an endless recursion when the Sequence
# is used.
#
public Sequence(public T type) ref is


  # create a list from this Sequence.
  #
  # A list is immutable, so it can be reused and shared between threads.
  # Compared to a stream, a list may require more (heap) allocation.
  #
  # Default implementation uses as_stream. Heirs must redefine at least
  # one of as_list or as_stream.
  #
  public as_list list T => as_stream.as_list


  # create a stream of T.
  #
  # A stream contains mutable state, so it cannot be reused or shared
  # between threads.
  #
  # Default implementation uses as_list.  Heirs must redefine at least
  # one of as_list or as_stream.
  #
  public as_stream stream T => as_list.as_stream


  # is this sequence known to be finite?  For infinite sequences, features like
  # count diverge.
  #
  public finite => false


  # is this Sequence known to be array backed? If so, this means that operations
  # like index[] are fast.
  #
  public is_array_backed => false


  # is this Sequence empty?
  #
  public is_empty => as_list.is_empty


  # count the number of elements in this Sequence.  Note that this typically
  # runs forever if executed on an endless list
  #
  # For lists that are not array backed, this might require time in O(count).
  #
  public count
  pre
    analysis: finite  # in practice, we do not always have this information
  => (map i32 (_ -> 1)).fold i32.sum


  # get the first element of this Sequence
  #
  # Sequence must not be empty, causes precondition failure if debug is enabled.
  #
  public first
  pre
    debug: !is_empty
  => as_list.head.get


  # get the first element of this Sequence or default if sequence is empty
  #
  public first(default T)
  =>
    if is_empty then default else first


  # get the last element of this Sequence
  #
  # Sequence must not be empty, causes precondition failure if debug is enabled.
  #
  # This may take time in O(count), in particular, it may not terminate
  # for an infinite Sequence.
  #
  public last
  pre
    debug: !is_empty
  => as_list.last


  # get the last element of this Sequence or default if sequence is empty
  #
  public last(default T)
  =>
    if is_empty then default else last


  # collect the contents of this Sequence into an array
  #
  public as_array array T =>
    as_list.as_array


  # create an array backed version of this sequence in case this is not array
  # backed.  This will ensure that operations like index[] or drop perform
  # in constant time.
  #
  # returns Sequence.this if is_array_backed.
  #
  public as_array_backed Sequence T
  post
    result.is_array_backed
  =>
    if is_array_backed
      Sequence.this
    else
      as_array


  # create a list and call 'for_each f' on it
  #
  public for_each(f T -> unit) unit => as_list.for_each f


  # calls `f` for element in the Sequence.
  #
  # Unlike `for_each` this returns itself
  # allowing easier composition with
  # other Sequence features.
  #
  # example:
  # [1,2,3,4,5]
  #   .filter is_prime
  #   .peek say
  #   .drop_while <10
  #
  public peek (f T -> unit) =>
    as_list ? nil    =>
            | c Cons => f c.head; c.tail.peek f
    as_list


  # consume all elements of this Sequence by f. This is an infix operator alias
  # for for_each.
  #
  # Ex.: To print all the elements of a list, you can use
  #
  #  1..10 ! say
  #
  public infix ! (f T -> unit) => for_each f


  # apply 'f' to each element 'e' as long as 'f e'
  #
  public for_while(f T -> bool) unit =>
    take_while f
      .for_each _->unit


  # create a new list that contains the first elements of
  # this Sequence for which 'f e' is false
  #
  public before(f T -> bool) list T =>
    take_while (x -> !(f x))


  # filter elements using predicate f
  # values for which f is false are dropped
  #
  public filter   (f T -> bool) list T => as_list.filter f


  # filter elements using predicate f, infix operator
  # synonym of filter.
  #
  public infix |& (f T -> bool) => filter f


  # filter elements using predicate f, infix operator
  # synonym of filter.
  #
  # NYI: What is better, 'infix |&' or 'infix &', or something else?
  #
  public infix & (f T -> bool) => filter f


  # check if predicate f holds for all elements
  #
  public infix ∀ (f T -> bool) bool => as_list ∀ f


  # check if predicate f holds for at least one element
  #
  public infix ∃ (f T -> bool) bool => as_list ∃ f


  # create a list that consists only of the first n elements of this
  # Sequence, fewer if this Sequence has fewer elements
  #
  public take (n i32) => as_list.take n


  # reverse the order of the elements in this Sequence
  #
  public reverse Sequence T => as_list.reverse_list


  # create a list that consists of the elements of this Sequence except the first
  # n elements
  #
  # NOTE: this may have performance in O(n) unless it is backed by an immutable array.
  #
  #
  public drop (n i32) => as_list.drop n


  # get a function that, given an index, returns the element at that index
  #
  public index [] () i32 -> T => n -> Sequence.this[n]


  # create a slice from this Sequence that consists of the elements starting at index
  # from (including) up to index to (excluding).
  #
  public slice(from, to i32) Sequence T =>
    drop(from).take to-from


  # create a tuple of two Sequences by splitting this at the given index, i.e.,
  # a Sequence of length 'at' and one of length 'count-at'.
  #
  # at may be <= 0 or >= count, in which case the resulting tuple will be the
  # (empty list, Sequence.this.as_list) or (Sequence.this.as_list, empty list), resp.
  #
  public split_at(at i32) => ((take at), (drop at))

  # Lazily take the first elements of this Sequence for which predicate 'p' holds.
  #
  public take_while (p T -> bool) => as_list.take_while p


  # Lazily drop the first elements of this Sequence for which predicate 'p' holds.
  #
  public drop_while (p T -> bool) => as_list.drop_while p


  # create a Sequence that consists of all the elements of this Sequence followed
  # by all the elements of s
  #
  public concat_sequences (s Sequence T) list T => as_list.concat s.as_list


  # infix operand synonym for concat_sequences
  #
  public infix ++ (s Sequence T) => as_list ++ s.as_list


  # create a list that repeats the current Sequence indefinitely.  In case 'Sequence.this'
  # is empty, returns 'nil'
  #
  public cycle list T => as_list.cycle


  # create a lazy list of all the tails of this Sequence, including the complete Sequence
  # as a list and the empty list 'nil'.
  #
  public tails list (list T) => as_list.tails


  # create a string representation of this Sequence including all the string
  # representations of its contents, separated by ',' and enclosed in '['
  # and ']'.
  #
  # In case this Sequence is known to be `finite` or has at most (Sequence T).type
  # .AS_STRING_NON_FINITE_MAX_ELEMENTS elements, all elements will be shown in the
  # resulting string. Otherwise, only the first elements will be shown followed by
  # ",…" as in "[1,2,3,4,5,6,7,8,9,10,…]".
  #
  # To force printing of all elements of a finite `Sequence` for which `finite` is
  # false (which may be the case since a Sequence in general might not know that it
  # if finite), you may use `as_string_all`.
  #
  public redef as_string =>
    if finite
      as_string_all
    else
      max := (Sequence T).AS_STRING_NON_FINITE_MAX_ELEMENTS
      (zip i32 String /* NYI: BUG #2527: type inference not powerful enough to infer `i32` here */
           0..max (v,n -> if n < max then $v else "…")).as_string_all


  # create a string representation of this Sequence including all the string
  # representations of its contents, separated by ", " and enclosed in '['
  # and ']'.
  #
  # NOTE: In case this Sequence is not finite, this will attempt to create an
  # infinitely long string resulting in failure due to resource exchaustion.
  #
  public as_string_all => "[{as_string ", "}]"


  # create a string representation of this Sequence including all the string
  # representations of its contents, separated by 'sep'.
  #
  # NOTE: In case this Sequence is not finite, this will attempt to create an
  # infinitely long string resulting in failure due to resource exchaustion.
  #
  public as_string (sep String) => as_list.as_string sep


  # call 'as_string' on the elements
  #
  public as_strings => map c->c.as_string


  # map the Sequence to a new Sequence applying function f to all elements
  #
  # This performs a lazy mapping, f is called only when the elements
  # in the resulting list are accessed.
  #
  public map(B type, f Unary B T) Sequence B =>
    as_list.map_to_list f


  # map the Sequence to a new Sequence applying function f to all elements
  #
  # This is an infix operator alias of map enabling piping code like
  #
  #   l := 1..10 | *10 | 300-
  #
  # to obtain 290,280,270,...200
  #
  # Note that map and therefore also this operator is lazy, so
  #
  #   _ := (1..10 | say)
  #
  # will not print anything while
  #
  #   (1..10 | say).for_each _->unit
  #
  # will print the elements since `for_each` is not lazy.
  #
  public infix | (B type, f Unary B T) Sequence B => map f


  # map each element of this Sequence to Sequence
  # Then flatten the result by one level,
  # essentially combining all the sequences.
  #
  public flat_map(B type, f T -> Sequence B) Sequence B =>
    as_list.flat_map_to_list f


  # Map this Sequence to f applied to neighboring pairs of values
  # in this Sequence.
  #
  # In case this Sequence has less than two elements, the result will
  # be the empty list.
  #
  # ex. to obtain a list of differences you, you may use `map_pairs (-)`:
  #
  #   [2,3,5,7,11,13,17,19,23,29].map_pairs a,b->b-a
  #
  # results in `[1,2,2,4,2,4,2,4,6]`
  #
  public map_pairs(B type, f (T,T)->B) list B =>
    zip (drop 1) f


  # fold the elements of this Sequence using the given monoid.
  #
  # e.g., to sum the elements of a Sequence of i32, use s.fold i32.sum
  #
  public fold (m Monoid T) => as_list.fold m.e m


  # fold the elements of this non-empty Sequence using the given function
  #
  # e.g., to find the minimum of a Sequence of i32, use `s.fold1 (<=)`
  #
  public fold1 (f (T,T)->T) T
  pre
    debug: !is_empty
  =>
    as_list.fold1 f


  # fold the elements of this Sequence using the given function and initial
  # value.
  #
  # In case this Sequence is empty, the result is `e`.
  #
  # e.g., to find the product of a Sequence of i32, use `s.foldf (*) 1`
  #
  public foldf (B type, f (B,T)->B, e B) B
  =>
    as_list.foldf_list f e


  # map this Sequence to a list that contains the result of folding
  # all prefixes using the given monoid.
  #
  # e.g., for a Sequence of i32 s, s.scan i32.sum creates a list of
  # partial sums (0..).map x->(s.take x).fold i32.sum
  #
  public scan (m Monoid T) => as_list.scan m.e m


  # reduce this Sequence to R with an initial value init
  # and a reducing function f.
  # the reduction is finished once f yields abort or
  # if the end of the sequence is reached.
  #
  public reduce(R type, init R, f (R,T) -> R | abort R) R =>
    match as_list
      nil => init
      c Cons =>
        match f init c.head
          a abort => a.val
          r R => c.tail.reduce r f


  # reduce this Sequence to `outcome R`
  # with an initial value `init` and a reducing function `f`.
  # the reduction is finished once `f` yields `abort` or
  # if the end of the sequence is reached.
  #
  public reduce_or_error(R type, init R, f (R,T) -> R | abort (outcome R)) outcome R =>
    match as_list
      nil => init
      c Cons =>
        match f init c.head
          a abort => a.val
          r R => c.tail.reduce_or_error r f


  # insert element v at position at
  #
  public insert(at i32, v T)
  pre
    debug: at ≥ 0
  =>
    take at ++ [v] ++ drop at


  # sort this Sequence using the total order defined by less_or_equal
  public sort(less_or_equal (T, T) -> bool) => container.sorted_array Sequence.this less_or_equal


  # sort this Sequence using total order defined for 'f a[i]'
  public sort_by(O type : property.orderable, f T->O) => sort (a, b -> f a ≤ f b)


  # create a new list from the result of applying 'f' to the
  # elements of this Sequence and 'b' in order.
  #
  public zip(U,V type, b Sequence U, f (T,U)->V) => as_list.zip0 b.as_list f


  # create a new Sequence from the result of applying 'f' to the
  # elements all combinations of elements of this Sequence and
  # all elements of 'b' iterating of 'b' repeatedly as follows
  #
  #   Sequence.this[0]  , b[0]
  #   Sequence.this[0]  , b[1]
  #   Sequence.this[0]  , b[2]
  #   Sequence.this[0]  , ...
  #   Sequence.this[0]  , b.last
  #   Sequence.this[1]  , b[0]
  #   Sequence.this[1]  , b[1]
  #   Sequence.this[1]  , ...
  #   ...               , ...
  #   Sequence.this.last, b.last
  #
  public combine(U, V type, b Sequence U, f (T,U)->V) Sequence V =>
    flat_map V x->(b.map y->(f x y))


  # create a new Sequence from tuples of all combinations of elements
  # of this Sequence and all elements of 'b' iterating of 'b' repeatedly
  # as follows
  #
  #   (Sequence.this[0]  , b[0]  )
  #   (Sequence.this[0]  , b[1]  )
  #   (Sequence.this[0]  , b[2]  )
  #   (Sequence.this[0]  , ...   )
  #   (Sequence.this[0]  , b.last)
  #   (Sequence.this[1]  , b[0]  )
  #   (Sequence.this[1]  , b[1]  )
  #   (Sequence.this[1]  , ...   )
  #   (...               , ...   )
  #   (Sequence.this.last, b.last)
  #
  public pairs(U type, b Sequence U) Sequence ((T, U)) =>
    combine b x,y->(x,y)


  # takes a transducer xf, a reducer f and an initial value
  # returns the result of applying the reducer xf f to the Sequence
  public transduce(TA,U type, xf transducer TA U T, rf (TA,U) -> TA, init TA) TA =>
    red := xf.call rf
    for
      res := init, red.call res el
      el in Sequence.this do
    else
      res


  # apply transducer to sequence, returning a sequence of results
  #
  # example usage:
  # human(age i32) is
  # ages := map (Sequence i32) human i32 (x -> x.age)
  # gt_ten := filter (Sequence i32) i32 (x -> x > 10)
  # xf := ages ∘ gt_ten
  # say ([human(4), human(12), human(30)].into xf) # [12,30]
  public into(TA type, xf transducer (Sequence TA) TA T) list TA =>
    red := xf.call ((res,val) -> res ++ [val])
    for
      res (list TA) := (list TA).empty , (red.call res el).as_list
      el in Sequence.this do
    else
      res


  # the nth element in the sequence if it exists, wrapped in an option,
  # nil otherwise.
  #
  public nth(n i32) option T
    pre
      debug: n ≥ 0
  =>
    match drop n
      nil => nil
      c Cons => c.head


  # the nth element in the sequence, must exist
  #
  public index [] (i i32) T
    pre
      debug: 0 ≤ i
      debug: finite: i < count
      debug: !finite: !(drop i-1).is_empty
  =>
    (nth i).get


  # adds the corresponding index to
  # every element in the sequence
  #
  public indexed list (tuple i32 T) =>
    indexed 0


  # adds an index to every element
  # in the sequence starting at start_idx
  #
  public indexed(I type : has_interval, start_idx I) list (tuple I T) =>
    # NYI: This should work without the type hints again.
    zip I (tuple I T) (start_idx..) (a,b -> (b, a))



  # chop this Sequence into chunks of `chunk_size`.
  # the last chunk may be smaller than `chunk_size`.
  #
  public chunk(chunk_size i32) list (list T)
  pre chunk_size > 0
  =>
    if is_empty
      nil
    else
      (take chunk_size).as_list : ((drop chunk_size).chunk chunk_size)


  # monoid of Sequences with infix concatentation operation.
  #
  public type.concat_monoid : Monoid (Sequence T) is

    # associative operation
    #
    infix ∙ (a, b Sequence T) Sequence T => a.concat_sequences b

    # identity element
    #
    e Sequence T =>
      (list T).empty


  # Maximum number of elements shown for on a call to `as_string` for a non-finite
  # Sequence.
  #
  public type.AS_STRING_NON_FINITE_MAX_ELEMENTS => 10
last changed: 2024-03-07