Feel.Runtime.Iteration (feel v0.1.0)

Copy Markdown View Source

for, some and every (DMN 1.6, section 10.3.2.14 and Table 50; D-86).

A backend does not loop. It gives this module the domains, the body, and where they are in the source, and this module runs the loops, so both backends iterate in the same way, count the same iterations, and end at the same limit. A domain and the body are functions of the environment, because they are code of the expression: the interpreter's walk of a node, and a fun of the generated code.

  • a domain is fn env, earlier -> {raw, env} end, where earlier is the list of the values of the iterators before it. A later domain reads the earlier iterators (for x in [[1, 2], [3, 4]], y in x, TCK 0084 #015), so it is evaluated again for each of their values. raw is a value, or {:through, from, to} for from..to, which has no value of its own. With the :reads option (below) earlier is only the values the domain reads.
  • the body is fn env, values, partial -> {value, env} end, with the value of each iterator, in order, and the results so far (partial; [] when the body does not read it). With the :partial option (below) the results are given as the list, oldest first (true), or as the results a body indexes (:view).

What a domain is given

A domain is given the values of the iterators before it, all of them, as a list. That is a list of k values for the domain of the kth iterator, and the backend that binds them by name does k things with it, so a loop of n iterators costs n * n / 2 whatever its domains read (D-86). The :reads option of for_loop/4, some/4 and every/4 is a list with one entry for each domain, :all or the places (0 for the first iterator) of the ones the domain reads, and a domain with places is given the values at them, in that order, and no others. The backends give places to the domains that have many iterators before them (more than 16) and leave :all for the rest. Without the option every domain is given all of them, as it was before it, so code that was made without it runs as it did.

What a body is given as partial

A body that reads partial as a value (sum(partial), partial as an argument, a path, a filter that is not an index, a nested loop over it) is given the list of the results so far, oldest first: the loop keeps them newest first, to put one in with a cons, and makes the list for each run of the body, which is m cells for the mth. That is what the :partial option true says. A body that reads it only as partial[expr] or count(partial) says :view, and is given the results as Feel.Runtime.Partial (D-86), that the loop puts each result in as it makes it and the reads look in, so there is no list to make and a read does not depend on how many results there are (Feel.Analysis.Iterations.view/2 is where that is decided, and the backends read it with Partial.at/4 and Partial.count/3). A loop that is told true and does not read partial is as it was.

The loops

The first iterator is the outermost loop and the last varies fastest, so for i in [1, 2], j in [3, 4] is (1, 3), (1, 4), (2, 3), (2, 4). for is the list of the bodies, in that order; some is false or r1 or r2 or ... and every is true and r1 and r2 and ..., in the three-valued logic (Feel.Runtime.Logic): a true decides a some and a false an every, whatever else is null. Every iteration is evaluated, as every operand of an operator is (D-53), so what a body reports is reported. A product with no element is [], false for some and true for every.

A strict quantifier

some and every of a boxed expression (strict: true, Feel.Runtime.Boxed, D-212) are null with an error, for the whole expression, when the body is a value that is neither a boolean nor null: the loop ends there. Without the option a body that is not a boolean is null for the fold, as the three-valued logic has it.

The domains

The domainThe elements
a listits elements, in order
from..to of whole numbersfrom, from + 1, ..., to, or down by 1 when to is below
from..to of dateseach day from from to to, or down
any other value, not nullthe value alone, as a list of one (section 10.3.2.9.4), as an info

Both ends of from..to are included. These are null, with an error, for the whole expression, and nothing runs: null as a domain; a range value, [1..2], which is not 1..2 (TCK 0084 #023 to #025); and from..to of anything but two whole numbers or two dates ("a".."z", 1.5..4, times, date and times, durations, null, a number and a date).

Limits

Every element that an iterator takes counts as an iteration, and the run ends, in either mode, with an error of kind :limit when they pass max_iterations (Feel.Runtime.Env.count_iteration/1). A range is walked, and not made, so for i in 1..1000000000 return i stops at the limit and not at the memory.

The list that a for makes is counted as it grows, and the run ends in the same way at the first result over max_list_length (Feel.Runtime.Env.check_list_length/2), where it ended when the finished list was refused. The error is the same, and carries the diagnostics of the run until that result and not those of the iterations that were left (D-154). A for inside another has a list of its own, and some and every make none.

Summary

Types

What the loop is told besides the code: the span of the expression, the span of each domain (for the diagnostics about it), whether the body reads partial and how (:partial: false, true for the list of the results, :view for Feel.Runtime.Partial), and what each domain reads (:reads, above).

What a domain gives: a value, or the two ends of from..to.

Functions

every: true and r1 and r2 ... for the results of the body.

for: the list of the values of the body for each iteration, or null with the error of a domain that cannot be iterated.

some: false or r1 or r2 ... for the results of the body.

Types

body()

domain()

@type domain() :: (Feel.Runtime.Env.t(), [Feel.Value.t()] ->
               {raw(), Feel.Runtime.Env.t()})

options()

@type options() :: %{
  :span => Feel.Diagnostic.span(),
  :spans => [Feel.Diagnostic.span()],
  optional(:partial) => boolean() | :view,
  optional(:strict) => boolean(),
  optional(:reads) => [:all | [non_neg_integer()]]
}

What the loop is told besides the code: the span of the expression, the span of each domain (for the diagnostics about it), whether the body reads partial and how (:partial: false, true for the list of the results, :view for Feel.Runtime.Partial), and what each domain reads (:reads, above).

raw()

@type raw() :: Feel.Value.t() | {:through, Feel.Value.t(), Feel.Value.t()}

What a domain gives: a value, or the two ends of from..to.

Functions

every(env, domains, body, options)

every: true and r1 and r2 ... for the results of the body.

iex> env = Feel.Runtime.Env.new()
iex> options = %{span: {0, 5}, spans: [{0, 1}]}
iex> body = fn env, [i], _partial -> {i != nil and i > 0, env} end
iex> {false, _env} = Feel.Runtime.Iteration.every(env, [fn env, [] -> {[1, nil], env} end], body, options)
iex> {true, _env} = Feel.Runtime.Iteration.every(env, [fn env, [] -> {[], env} end], body, options)

for_loop(env, domains, body, options)

@spec for_loop(Feel.Runtime.Env.t(), [domain()], body(), options()) ::
  {Feel.Value.t(), Feel.Runtime.Env.t()}

for: the list of the values of the body for each iteration, or null with the error of a domain that cannot be iterated.

iex> env = Feel.Runtime.Env.new()
iex> domains = [fn env, [] -> {[Decimal.new(1), Decimal.new(2)], env} end]
iex> body = fn env, [i], _partial -> {Decimal.add(i, 10), env} end
iex> {values, _env} = Feel.Runtime.Iteration.for_loop(env, domains, body, %{span: {0, 5}, spans: [{0, 1}]})
iex> Enum.map(values, &Decimal.to_integer/1)
[11, 12]

some(env, domains, body, options)

some: false or r1 or r2 ... for the results of the body.

iex> env = Feel.Runtime.Env.new()
iex> options = %{span: {0, 5}, spans: [{0, 1}]}
iex> body = fn env, [i], _partial -> {i == 2, env} end
iex> {true, _env} = Feel.Runtime.Iteration.some(env, [fn env, [] -> {[1, 2, 3], env} end], body, options)
iex> {false, _env} = Feel.Runtime.Iteration.some(env, [fn env, [] -> {[], env} end], body, options)