HNHacker News
TopNewBestAskShowJobs

vpetrovykh

10 karma · joined October 19, 2015

submissionscomments
vpetrovykh··on We Can Do Better Than SQL
A few things come to mind:

1) It's a little unlikely that at the same time you have data where an {} is an error, but you are not making the property required AND with knowledge of that you still don't bother with other validation approaches. The point is that if you're aware that this property is potentially incorrect, you would want to check or restrict it. But yes, if this situation is completely unexpected, then "sum" won't notice any issues.

2) You have to remember that {} can arise from perfectly normal operations, such as filtering. So if you filter by a certain date range and there's no Payments there, then "(SELECT Payment FILTER .timestamp > <datetime>$date).amount" expression becomes {} even if the "amount" property itself is required. So doing a sum over it is simply a question of "What's the total amount in payments since $date?" and if there aren't any payments, the answer is 0, not {}. Plus what you certainly don't want is to get an {} from the "sum" here and do "{} + 100" to add some other charge (perhaps a sum from a different account) and still end up with {}, which now creates an error in a situation where there's nothing wrong with the data.

3) Rather than imbuing {} with special meaning to signal errors, a separate property would be more appropriate. Such as a boolean "valid" flag that gets set after the record checks out or can even be a dynamically computable expression that looks at the record (say, Payments from our example) and does something like "valid := EXISTS .amount". Then you'd filter things by the valid property before feeding them to "sum" like so:

  SELECT sum( (SELECT Payment FILTER .valid).amount );
vpetrovykh··on We Can Do Better Than SQL
This type of error is better remedied by making the balance property be required (so that it cannot be set to {} and produce an exception at the time the error is introduced). This way you will know about the error early enough. The point is that if an empty value is NOT valid then forbidding it at schema level is the best solution. So required keyword is going to do that for you.

Alternatively, if making the property required is not possible due to some workflow constraints, you could do "SELECT Payment{customer} FILTER NOT EXISTS .balance" to find all payments (and the associated customer), which don't have any balance set. Then once you know what they are you might use "UPDATE" to fix the problem.

Empty sets have fairly well-defined and consistent behavior w.r.t. functions (and operators). You learn it once and it applies in all contexts - specifically that empty sets are just sets like any other.

vpetrovykh··on We Can Do Better Than SQL
Oh, that's simple: sum(A UNION B) should be the same as sum(A) + sum(B) for any two sets A and B (or else there would be very weird inconsistencies).

sum(A UNION {}) = sum(A) + sum({})

sum(A) = sum(A) + sum({})

0 = sum({})

Typically for any operation generalized for a set the result of op({}) should be equal to the identity for that operation (0 for sum, 1 for product, True for AND, False for OR, etc.). It's always such a value I that for any other value A, A op I = A.

vpetrovykh··on We Can Do Better Than SQL
You are exactly right about everything consuming sets in EdgeDB. Even when a function is defined on scalars, it's really defined on singleton sets. Literals are also singleton sets, so "1" and "{1}" are equivalent and so are "foo(1)" and "foo({1})". Usually we omit the set braces for singleton values to reduce visual noise.
vpetrovykh··on We Can Do Better Than SQL
Let me make a tiny correction to the expression you wrote:

"sum({1, 1, {}})" - the function sum takes only one argument and it's a set. Because we flatten all "nested" sets, the expression "{1, 1, {}}" is equivalent to "{1} UNION {1} UNION {}".

The expression "1 + 1 + {}" albeit valid grammatically, can be equivalently re-written as "{1} + {1} +{}". At this point it should be far more obvious why "sum({1} UNION {1} UNION {})" is not the same as "{1} + {1} + {}".

Literals may be a little confusing because they look like elements, but they are still sets, singleton sets, specifically. There's practical value in simply thinking about "a bunch of things: A, B, C", where each of the A, B and C can themselves be empty, a single thing, or a bunch of things while ignoring nesting. In our case we allow duplication in these bunches (which is not part of the bunch theory: http://www.cs.toronto.edu/~hehner/bunch.pdf). However, because most people are familiar with sets we find it easier to keep using the terms "set" and "multi-set" (and stipulate that they are flattened) in explanations.

In general, the way the operator "+" works is this: A + B = {a + b : for all a in A, for all b in B}. Whereas the expression "{A, B}" is defined to be equivalent to "A UNION B".

vpetrovykh··on We Can Do Better Than SQL
The sum of an empty set is, in fact, 0 (the identity for addition).

The generalized conjuction (we have a function called "all" for that) of an empty set is True (the identity for conjunction).

The generalized disjuction (we have a function called "any" for that) of an empty set is False (the identity for disjunction).

All of the above "sum", "all", and "any" are basically aggregate functions that operate on sets as a whole.

There is no special logic that you wouldn't get from considering these operations generalized for a set.

vpetrovykh··on We Can Do Better Than SQL
If a NULL were just a value in 3-valued logic, it could have been OK. However, that's not how it works out. Consider the following (that should be equivalent if NULL is just like Maybe in True-Maybe-False logic):

  SELECT null AND true;

  SELECT bool_and(column1) FROM (VALUES (null::bool),(true)) AS foo;
In PostgreSQL the first query produces NULL, while the second produces TRUE. Yet, it's also possible to get NULL as a result of aggregating values with bool_and:

  SELECT bool_and(column1) FROM (VALUES (null::bool),(null::bool)) AS foo;
And that's not how a 3-valued logic works.
vpetrovykh··on Show HN: EdgeDB – Next generation database
You seem to treat NULLs as if they have a specific meaning. So then the question is this: what should count(NULL) be?

Here are some options:

1) count(NULL) = 0, so apparently NULL is just like an empty set at least some of the time. This means that some of the code will treat NULLs as empty sets and other code will not, leaving the burden on the programmer to keep in mind these implicit differences.

2) count(NULL) = 1 because NULL is a value, albeit a sentinel value. This can lead to tricky problems where a count() suggests that there's some data, while, in fact, there is none.

3) count(NULL) = NULL. If the idea behind this option is that sentinel value cannot be operated on, then this is pretty much like throwing an error at every NULL, which, in turn, will result in the necessity to guard many expressions with some error (NULL) handling code.

One of the things to note is that an empty set has rather unambiguous semantics, while a NULL presents options each of which can be justified depending on how a particular person thinks about this special value. The line between "a value has not yet been assigned", "no value has been assigned" and "a value has been unassigned" is very blurry. On the other hand, the line between "there is no value" and "there is a value" is pretty clear.