4 ms·
> What is the order of the two calls to `query` actually matters, but how could the language know? The language can't "know the order" of these calls, since th
by chriswarbo 4y ago
> What is the order of the two calls to `query` actually matters, but how could the language know?
The language can't "know the order" of these calls, since they are not ordered. No information is passed from one call to the other, hence neither is in each other's past light cone.
If you want to impose some order, you can introduce a data-dependency between the calls; e.g. returning some sort of value from the "INSERT" call, and incorporating that into the "SELECT" call. Examples include:
- Some sort of 'response' from the database, e.g. indicating success/failure
- GHC's implementation of IO as passing around a unit value for the "RealWorld"
- Lamport clocks
- A hash of the previous state (git, block chains, etc.)
- The 'connection' value itself (most useful in conjunction with linear types, or equivalent, to prevent "stale" connections being re-used)
- Continuation-passing style (passing around the continuation/stacktrace)
> languages that don't strictly define it make this type of thing very error prone
On the contrary, attempting to define a total order on such spatially-separated events is very error prone. Attempting to impose such Newtonian assumptions on real-world systems, from CPU cores to geographically distributed systems, leads to all sorts of inconsistencies and problems.
This is another example of opt-ins being better than defaults. It's more useful and clear to have no implicit order of calculations imposed by default, so that everything is automatically concurrent/distributed. If we want to impose some ordering, we can do so using the above mechanisms.
Attempting to go the other way (trying to run serial programs with concurrent semantics) is awkward and error-prone. See: multithreading.
See also https://en.wikipedia.org/wiki/Relativistic_programming https://en.wikipedia.org/wiki/Relativistic_programming
Note that you haven't specified the database semantics either.
Perhaps the connection points to a 'snapshot' of the contents, like in Datomic; in which case doing an "INSERT" will not affect a "SELECT". In this case, a "SELECT" will only see the results of an "INSERT" if we query against an updated connection (i.e. if we introduce a data dependency!).
Perhaps performing multiple queries against the same connection causes the database history to branch into "multiple worlds": each correct on its own, but mutually-contradictory. That's how distributed systems tend to work; with various concensus algorithms to try and merge these different histories into some eventually-consistent whole.
PS: There is a well-defined answer in this example; since the "INSERT" query is dead code, it should never be evaluated ;)
PPS: Even in the "normal" case of executing these queries like statements, from top-to-bottom, against a "normal" SQL database, the semantics are under-defined. For example, if 'query' is asynchronous, the second query may race against the first (e.g. taking a faster path to a remote database and getting executed first). This can be prevented by making 'query' synchronous; however, that's just another way of saying we need a response from the database (i.e. a data dependency!)
- tsimionescu 4y agoIn most programming languages, the order of the two statements would be well defined, and neither would be dead code. Trying to make all statements implicitly concurrent unless they have explicit dependencies is a terrible way to complicate your life. That in some cases you can optimize the code (or the CPU will do it for you) by executing it in certain other orders where it is safe is supposed to remain an invisible optimization. It is obvious to everyone that distributed code, eventual consistency, and other similar non-totally-ordered examples are much harder to get right than procedural code. Even simple print-based debugging/logging becomes excessively complex if you get rid of the local total ordering. Even most network-based computing is done over TCP (or, more recently, QUIC) exactly because of how enormously useful having a total order is in practice (even if it's just an illusion/abstraction). > PS: There is a well-defined answer in this example; since the "INSERT" query is dead code, it should never be evaluated ;) It's only dead code if you assume Haskell's bizarre lazy execution model. In virtually every other language, unless the compiler can prove that query() has no side effects, the INSERT will be executed.
- chriswarbo 4y ago> In most programming languages, the order of the two statements would be well defined, and neither would be dead code. There are no statements in the above examples, just expressions (composed of other expressions). Evaluation order of expressions is not well defined in "most popular languages", e.g. consider this expression: printSecondArgument( query(connection, "INSERT INTO t1 VALUES ('abc')"), query(connection, "SELECT * FROM t1") ) > Trying to make all statements implicitly concurrent unless they have explicit dependencies is a terrible way to complicate your life I agree, that's one reason why I dislike statements (see my list in a parent comment) > It is obvious to everyone that distributed code, eventual consistency, and other similar non-totally-ordered examples are much harder to get right than procedural code. This is a category error. All code is distributed; the world is partially-ordered. "Procedural code" (i.e. serial/sequential execution) is a strategy for dealing with that. It's particularly easy (give each step a single dependency; its "predecessor"), but also maximally inefficient. That's often acceptable when running on a single machine, and sometimes acceptable for globally-distributed systems too (e.g. that's what a blockchain is). Forcing it by default leads to all sorts of complication (e.g. multithreading, "thread-safety", etc.). Making it opt-in gives us the option of concurrency, even if we write almost everything in some "Serial monad" (more likey, a continuation-passing transformer)