Program lattices, partial orderings and directed acyclic graphs are *the* critical foundational constructs that Flow is built upon, so I will attempt to describe here what these things are, why they matter to programming languages, and how they fit in the design of Flow.
Difference between dataflow programming languages and Flow
Dataflow programming languages (where you can build a program as a "circuit diagram") are overly complicated. Flow is very different from most other dataflow programming languages, you can't just connect a dataflow graph however you want in the style of old-school flow charts. Flow charts died out for a reason: they produce spaghetti code. Flowcharts -- and, by derivation, most dataflow languages, allow the user to make almost arbitrary connections between any part of the chart/graph and any other part of the graph, including introducing overlapping inter-nested loops of arbitrary complexity. This is directly correlated with the GOTO keyword in BASIC that died a just and horrible death.
For the purposes of describing the control flow of a program, old-school flowcharts were briefly superceded in the 90s (at least at my university) by a second generation of control flow diagramming tools: hierarchical program flow models, which were like a UML hierarchical call chart, only oriented vertically: you would show the program structure in a series of nested function calls, drawn as a tree. In effect, this enforced pure hierarchical containment of control flow, i.e. it forced you to design a control flow for your code that was in essence purely functional in style.
The Flow model introduces a third paradigm, that of program lattices. A lattice is a partial ordering and a DAG by definition, as described below.
(1) Directed Acyclic Graphs (DAGs)
Most imperative programming languages conflate the concept of variables and values: the action of reading from a variable returns its *current value*, and because (except when working with constants) imperative languages primarily concern themselves with variables, the actual values those variables hold cannot be known at compiletime. Consequently, the data dependency graph in an imperative language (what needs to be computed before something else can be computed from it) is also a graph of dependencies between variables, not values, and it is entirely possible to create directed data dependency cycles in your program, for example if you have the statement, "i = i + 1" inside a loop.
The problem with this is that underlying the variable data dependency graph (the "compiletime data dependency graph") is something that might be called the true "data operand graph" (or the "runtime data dependency graph"), and this is defined by values, not variables. If you were to record exactly what happens during a specific run through the program, every time a variable is updated, you could create a new "value node" in the graph, and when the current value of a variable is read, you would add an edge to a graph that points to the specific value node that corresponds to the current value of the variable.
In this model, it is easy to see where race conditions come from in a multithreaded context: if another thread pushes a value into a variable at an unexpected time, this actually changes the target node in the underlying "data operand graph" for the edge that corresponds to the the next read from the variable. In other words, the entire connectivity of the true underlying data dependency graph is at the mercy of the relative timing of reads and writes.
However if we remove the concept of "the current value of a variable" from the programming language, and replace it with "the one and only value this variable will ever take on" (immutability) or "the value of this variable at a specific timestamp" (syntactic sugar that allows aliasing of variables with a parameter that means something to the user and the compiler, e.g. "x(t) = x(t-1) + 1"), then this problem disappears.
In fact it turns out that the only compilable programs in a language with this constraint have compiletime and runtime data dependency graphs that are exactly the same, and that are DAGs, i.e. the language is syntactically constrained such that all valid programs have variable reference graphs that *are* the precise runtime data dependency graphs.
Functional programming languages already satisfy this, because all variables actually represent immutable values, but the requirement that the program's structure must exactly describe the runtime data dependency graph is actually in some ways a weaker condition than that imposed by functional programming, because it's possible to support a subset of imperative-style "scatter" computation in the DAG model whereas the functional style only really describes "gather" computation.
(2) Partial orderings
The Flow model introduces a third paradigm, that of program lattices. A lattice is a partial ordering and a DAG by definition, as described below.
(1) Directed Acyclic Graphs (DAGs)
http://en.wikipedia.org/wiki/Directed_acyclic_graph
Definition
Loosely defined, these are basically like a forest of rooted trees (meaning trees with arcs/edges directed away from the root) whose nodes may branch out but whose branches may also join together (coalesce) as you move away from the root.
More formally, and by very definition, a DAG is an arbitrary directed graph that does not possess a cycle: there is simply no way to follow a path from any start node and end up back at the same start node again after a nonzero number of moves. (Note that if you take away the directionality of an arc, turning it into an undirected edge, the graph may indeed possess cycles, but in the directed form.)
Implication for programming language design
Definition
Loosely defined, these are basically like a forest of rooted trees (meaning trees with arcs/edges directed away from the root) whose nodes may branch out but whose branches may also join together (coalesce) as you move away from the root.
More formally, and by very definition, a DAG is an arbitrary directed graph that does not possess a cycle: there is simply no way to follow a path from any start node and end up back at the same start node again after a nonzero number of moves. (Note that if you take away the directionality of an arc, turning it into an undirected edge, the graph may indeed possess cycles, but in the directed form.)
Implication for programming language design
Most imperative programming languages conflate the concept of variables and values: the action of reading from a variable returns its *current value*, and because (except when working with constants) imperative languages primarily concern themselves with variables, the actual values those variables hold cannot be known at compiletime. Consequently, the data dependency graph in an imperative language (what needs to be computed before something else can be computed from it) is also a graph of dependencies between variables, not values, and it is entirely possible to create directed data dependency cycles in your program, for example if you have the statement, "i = i + 1" inside a loop.
The problem with this is that underlying the variable data dependency graph (the "compiletime data dependency graph") is something that might be called the true "data operand graph" (or the "runtime data dependency graph"), and this is defined by values, not variables. If you were to record exactly what happens during a specific run through the program, every time a variable is updated, you could create a new "value node" in the graph, and when the current value of a variable is read, you would add an edge to a graph that points to the specific value node that corresponds to the current value of the variable.
In this model, it is easy to see where race conditions come from in a multithreaded context: if another thread pushes a value into a variable at an unexpected time, this actually changes the target node in the underlying "data operand graph" for the edge that corresponds to the the next read from the variable. In other words, the entire connectivity of the true underlying data dependency graph is at the mercy of the relative timing of reads and writes.
However if we remove the concept of "the current value of a variable" from the programming language, and replace it with "the one and only value this variable will ever take on" (immutability) or "the value of this variable at a specific timestamp" (syntactic sugar that allows aliasing of variables with a parameter that means something to the user and the compiler, e.g. "x(t) = x(t-1) + 1"), then this problem disappears.
In fact it turns out that the only compilable programs in a language with this constraint have compiletime and runtime data dependency graphs that are exactly the same, and that are DAGs, i.e. the language is syntactically constrained such that all valid programs have variable reference graphs that *are* the precise runtime data dependency graphs.
Functional programming languages already satisfy this, because all variables actually represent immutable values, but the requirement that the program's structure must exactly describe the runtime data dependency graph is actually in some ways a weaker condition than that imposed by functional programming, because it's possible to support a subset of imperative-style "scatter" computation in the DAG model whereas the functional style only really describes "gather" computation.
(2) Partial orderings
http://en.wikipedia.org/wiki/Partial_ordering
Definition
A partial ordering is a DAG where the directions on the arcs/edges follow the arrow of time, in other words where A -> B indicates "A happens before B" (or, as a dependency graph, A <- B indicates "A must happen before B can happen").
Assuming that time moves downwards, and given the following graph,
A
/ \
B C
\ /
D
we have that A must happen before B and C, and B and C have to happen before D. However, this graph says *nothing* about the relative order in which B and C must happen: B can happen before C, or C can happen before B, or both can happen concurrently.
Implication for programming language design
Direct examination of a partial ordering can yield a concurrent execution plan for a parallel program: if there is no directed path between two nodes that passes through zero or more other nodes in a partial ordering of work units to be completed, then the work units corresponding to the two nodes can be completed in parallel.
If your programming language has syntactically enforced that every program's reference structure is a DAG, then every program is also a partial ordering, and by examining the source alone, a simple parallelization plan can be determined in many cases.
(3) Lattices
Definition
A partial ordering is a DAG where the directions on the arcs/edges follow the arrow of time, in other words where A -> B indicates "A happens before B" (or, as a dependency graph, A <- B indicates "A must happen before B can happen").
Assuming that time moves downwards, and given the following graph,
A
/ \
B C
\ /
D
we have that A must happen before B and C, and B and C have to happen before D. However, this graph says *nothing* about the relative order in which B and C must happen: B can happen before C, or C can happen before B, or both can happen concurrently.
Implication for programming language design
Direct examination of a partial ordering can yield a concurrent execution plan for a parallel program: if there is no directed path between two nodes that passes through zero or more other nodes in a partial ordering of work units to be completed, then the work units corresponding to the two nodes can be completed in parallel.
If your programming language has syntactically enforced that every program's reference structure is a DAG, then every program is also a partial ordering, and by examining the source alone, a simple parallelization plan can be determined in many cases.
(3) Lattices
http://en.wikipedia.org/wiki/Lattice_(order)
Definition
A lattice is a partial ordering with a single unique "top element" (least upper bound) and a single unique "bottom element" (greatest lower bound). Every node in the lattice is reachable by following some directed path from the single least upper bound, and there is a directed path from every node in the lattice to the single least lower bound.
Implication for programming language design
In a programming language, the "top element" could be thought of as the start state of the program, and any non-unique greatest lower bound indicates some value that was computed but not used before the program terminates at the "bottom element", and therefore may be pruned as an unused value. Program DAGs pruned of unused computation become lattices. Hence if the compiler is doing its job, a Flow program will really be a lattice and not just a DAG.
Definition
A lattice is a partial ordering with a single unique "top element" (least upper bound) and a single unique "bottom element" (greatest lower bound). Every node in the lattice is reachable by following some directed path from the single least upper bound, and there is a directed path from every node in the lattice to the single least lower bound.
Implication for programming language design
In a programming language, the "top element" could be thought of as the start state of the program, and any non-unique greatest lower bound indicates some value that was computed but not used before the program terminates at the "bottom element", and therefore may be pruned as an unused value. Program DAGs pruned of unused computation become lattices. Hence if the compiler is doing its job, a Flow program will really be a lattice and not just a DAG.
Lattices in Flow
In Flow, a node of a DAG typically represents a collection, and the arcs coming into a node represent the data dependencies required to compute the contents of that collection (as well as, collectively, the operation that actually computes the contents of the collection based the values of its dependencies).
Arbitrary conditional control flow is specified in Flow by conditionally nesting/embedding sub-lattices within a bigger lattice (i.e. nodes in lattices can be recursively expanded into entire nested lattices -- basically this is like recursive function application).
Because, with program lattices, there are no cycles or back-links that violate the arrow of time, it is impossible in Flow to create spaghetti "circuit diagrams", or there are no arbitrarily inter-nested loops (i.e. no GOTO equivalent). However, you are also not forced into the pure functional mindset that emerges from requiring control flow to be purely the result of hierarchical/tree-like calls.
Structuring programs as lattices is not new per se, it's implicitly used in a weak form in "array programming" (performing elementwise operations on collections in parallel), but lattice-based computational models are not syntactically enforced elsewhere, and (it appears) are not yet used as the most important principle driving the design of programming languages other than Flow.