The Forum for Discussion about The Third Manifesto and Related Matters

You need to log in to create posts and topics.

Are inclusion dependencies reducible to foreign-key dependencies?

Quote from johnwcowan on October 13, 2019, 6:52 pm
Quote from Dave Voorhis on October 13, 2019, 5:53 pm

 

Sorry, I should have been clearer. I object to implicitly-applied conversions.

Okay, clear enough.  In languages with multiple sizes/precisions of numbers, though, I think Go's treatment of numeric literals is good: they pick up their type from the context, so both var i int32 = 30 and var j int64 = 30 and even var k float64 = 30 are all valid.  Otherwise you have to have separate literal markers for eight kinds of integers and two kinds of floats.

 

Andl - A New Database Language - andl.org
Quote from dandl on October 13, 2019, 11:49 pm

Fortran first appeared in 1957. Are you saying that it did something different for the first 4 years?

Yes.  So-called "mixed-mode arithmetic" was forbidden in Fortran I, II, and III; you had to call a conversion function explicitly to do an arithmetic operation, though not to do an assignment. This was considered extremely irritating, and the Fortran IV compiler was taught to do implicit coercions, as almost all languages do to this day.

I don't recall any type declarations in Fortran II/IV. Integers and reals were implicitly declared by usage in context and the first letter of the name.

In Fortran IV and Fortran 66, at least, variable type declarations were available; the first-letter rule was applied if no declaration was present.  ("God is real unless explicitly declared integer.")   There was no type inference.

Yes, the vast majority of languages permit implicit arithmetic widening on the grounds that it's relatively safe and highly convenient. Many languages convert during assignment. I don't think you can blame Fortran for what many language designers thought of as just common sense.

It's common sense because we've had it for 60 years.

<horrible code omitted>

What I'm saying is that although Java was intended to allow objects of different actual types to compare true in particular circumstances, it is very typical for Java implementers to defeat this mechanism and ensure that as many type-crossing equality comparisons as possible are false.

And you don't see this as 'funky and difficult'?

It's ugly, but all practical code is ugly in languages without syntax extension.

~~ smiles superciliously like a smug Lisp weenie ~~

 

 

Quote from Dave Voorhis on October 13, 2019, 7:42 pm
Quote from johnwcowan on October 13, 2019, 6:52 pm
Quote from Dave Voorhis on October 13, 2019, 5:53 pm

 

Sorry, I should have been clearer. I object to implicitly-applied conversions.

Okay, clear enough.  In languages with multiple sizes/precisions of numbers, though, I think Go's treatment of numeric literals is good: they pick up their type from the context, so both var i int32 = 30 and var j int64 = 30 and even var k float64 = 30 are all valid.  Otherwise you have to have separate literal markers for eight kinds of integers and two kinds of floats.

That seems reasonable, as there's nothing hidden.

Though if the Go designers had instead decided to provide literal markers for eight kinds of integers and two kinds of floats, that would be fine.

Using literal markers doesn't scale: how about complex numbers built over the eight+two numeric kinds? How about matrices built over them? How about matrices of complexes? By "how about" (before I get accused of how-aboutism) I mean: how do you integrate literals of those types into the language; how do you overload arithmetic operators?

With the Go example, how to combine literals in expressions? Such as

var i int32 = 30
var j int64 = 30
...

if i + 1 > 7 then ...

j := 1 + 7

We want to say literal 1 and 7 must mean int32, because 1 is added to int32 (with an int32 result) and 7 is compared to an int32. Or 1 + 7 is assigned to an int64.

To compare how it goes in Haskell: numeric literals are lexed to be of some numeric type, as supplied by the context. This is a general mechanism, not specific to numerics, for example String literals are taken to be of some Stringy type, as supplied by context. (Maybe packed/null-terminated or count-prefixed or in the worst case link-listed.)

Arithmetic operators are overloaded per type: they take two arguments of that type, and produce a result of that type. Comparison operators likewise take two arguments of the same type, and produce a Bool result.

So you can build arbitrarily complex expressions (including assignments) without any explicit type annotation nor casting -- providing there's mention of something with a declared type. Or you can go (longandcomplexexpression) `asTypeOf` i, where asTypeOf is a regular function that provides a type-context from its second argument but otherwise ignores that and doesn't try to evaluate. (With that behaviour, asTypeOf usually appears infixed, as I've done there with the backticks.)

As we all seem to be agreeing with: there's no implicitly-applied conversions in Haskell. In fact the typeclass mechanism (which is behind what's going on) was precisely to avoid the ad-hoc treatment of numeric types and equality/comparison in other Functional Languages of the time (1989). The designers wanted a principled way to deliver RM Pre 22 (in effect): some types support arithmetic, some types support comparisons, some types support equality-testing (always of values, not pointers nor object identities), some don't (you can't equality-test two functions, even though functions are first-class values/can be assigned to a variable/passed as an argument/result from a function invocation/etc). If you construct a type from equality-testable components, then the struct is equality-testable, and the compiler can generate the overloading for the == operator.

Oh, to come back to where this threadlet started: Haskell has no Inheritance/hierarchy of types, not even widths of integer. You must explicitly cast an int32 to int64 if you want to add/compare values of those types.

Haskell does have inheritance/hierarchy of typeclasses (that is, of overloadable behaviour/operations): all types declared as numeric must already be declared as comparable; all types declared as comparable must already be declared as equality-testable.

Quote from dandl on October 13, 2019, 11:49 pm

However, Java allows you to do value comparisons using the equals method. The comparison new Integer(3).equals(new String("blah")) is statically valid and returns false. The comparison new Integer(3).equals(new Integer(3)) returns true.

The vast majority of non-foundational Java classes, however, immediately check whether the two arguments of equalshave the exact same type and return false if they don't.  The general appearance of a modern Java equals method of class Foo is this:

<horrible code omitted>

And you don't see this as 'funky and difficult'?

It's perhaps verbose, but it's clear that all exceptional cases return false. An alternative is to ignore the exceptional cases and let the code throw exceptions. In other words, just do this:

@Override
public boolean equals(Object o) {
    Foo foo = (Foo) o;
    // field comparison
    return <some boolean expression performing the actual equality test>;
}

Type mismatches will throw an exception on the attempt to cast to Foo. A null parameter will throw relevant exceptions in the field comparison. Testing the parameter for equality with this should still return true per the equality test, but less efficiently. Etc.

I make no comment on which is better or worse.

I'm the forum administrator and lead developer of Rel. Email me at dave@armchair.mb.ca with the Subject 'TTM Forum'. Download Rel from https://reldb.org
Quote from AntC on October 14, 2019, 4:21 am
Quote from Dave Voorhis on October 13, 2019, 7:42 pm
Quote from johnwcowan on October 13, 2019, 6:52 pm
Quote from Dave Voorhis on October 13, 2019, 5:53 pm

 

Sorry, I should have been clearer. I object to implicitly-applied conversions.

Okay, clear enough.  In languages with multiple sizes/precisions of numbers, though, I think Go's treatment of numeric literals is good: they pick up their type from the context, so both var i int32 = 30 and var j int64 = 30 and even var k float64 = 30 are all valid.  Otherwise you have to have separate literal markers for eight kinds of integers and two kinds of floats.

That seems reasonable, as there's nothing hidden.

Though if the Go designers had instead decided to provide literal markers for eight kinds of integers and two kinds of floats, that would be fine.

Using literal markers doesn't scale: how about complex numbers built over the eight+two numeric kinds? How about matrices built over them? How about matrices of complexes? By "how about" (before I get accused of how-aboutism) I mean: how do you integrate literals of those types into the language; how do you overload arithmetic operators?

Literal markers scale as far as is needed. In languages that use them, a given literal denotes a value of a specific primitive type. E.g., 3 denotes an int, 3L denotes a long, 0.3 denotes a double, 0.3F denotes a float, etc.

All other types are composed of primitive types.

I'm the forum administrator and lead developer of Rel. Email me at dave@armchair.mb.ca with the Subject 'TTM Forum'. Download Rel from https://reldb.org
Quote from Dave Voorhis on October 13, 2019, 11:22 am
Quote from dandl on October 12, 2019, 11:43 pm
Quote from Brian S on October 12, 2019, 5:38 pm

The TTM definition poses some problems--especially when combined with the IM.  For instance:

ODD: INT | (X \ 2) * 2 != X

EVEN: INT | (X \ 2) * 2 == X

P {A ODD, B EVEN}, Q{A EVEN, B ODD}, R{A INT, B INT}

The comparison P == Q must valid under the IM, because the empty relation {A INT, B INT}{} can be assigned to all three of the relation variables.

I think you're layering a whole bunch of unnecessary detail. The IM either does or does not allow subtypes to be compared without an explicit conversion. The rest follows. Embedding values into tuples and relations and giving them conflicting names is irrelevant to the issue. ODD and EVEN are different types with a common super-type. The IM either considers comparisons between values of those types to be valid or not. It's a matter of fact, not opinion.

[I have no idea. The IM is a thing of mystery to me, but Dave and Hugh should certainly be able to say yea or nay.]

Per my recollection, that's an essay question, though as if I recall correctly, Date (at least) felt that it should be allowable to compare, say, INT to CHAR.

But given an expression like 3 = "blah" I'd argue that it should obviously and unconditionally be a static type mismatch under the IM, despite INT and CHAR being subtypes of ALPHA. In Rel, it throws an error.

This is similar to, say, Java, where attempts to compare (using the '==' operator) an Integer to a String fails as a type mismatch, despite Integer and String both being subclasses of Object. But note that '==' is an object identity comparison, not a value comparison. The result of new Integer(3) == new Integer(3) is false! (Which, per identity comparison, is true.)

However, Java allows you to do value comparisons using the equals method. The comparison new Integer(3).equals(new String("blah")) is statically valid and returns false. The comparison new Integer(3).equals(new Integer(3)) returns true.

Java's approach, though notionally obedient to the implications of a class hierarchy rooted in Object (and perhaps unavoidable given how the language works) -- which is notionally isomorphic to an IM subtype hierarchy rooted in ALPHA -- is weak. I'd argue that the same weakness applies to an IM that allows comparisons of INT to CHAR. It weakens type safety, and shouldn't be allowed.

That said, I wouldn't categorically preclude such comparisons, only require that they be made explicit. I.e., if you wish to be able to compare INT to CHAR, you are required to define an implementation of the '=' operator specific to INT and CHAR operands. That's how Rel works.

Static type checking of scalar expressions should throw an error if the only common supertype is ALPHA because the subtypes are by definition disjoint, so such operations don't make sense.  But that is not the case for relation expressions.

The issue is that REL {A ALPHA, B ALPHA}{} is an element of types REL {A ODD, B EVEN} and REL {A EVEN, B ODD}, as well as REL {A INT, B CHAR}.  As a consequence, comparisons of relation expressions having the same set of attribute names must be allowed under the IM, and furthermore, must return true when both relation expressions are empty.

One of the arguments against there being a single empty relation, namely, the empty set, is that static type checking can be performed for relation expressions, but as the above example shows, that is not the case for relation expressions with the same set of attribute names under the IM.

So either we ditch the IM, or rethink the heading/body definition of relations.  I prefer the latter.  A tuple is a mapping from a set of attribute names onto a set of values.  A relation is a set of tuples having the same set of attribute names.  Then there is only one empty relation, namely, the empty set.  This only requires checking whether relation expressions are empty before making the comparison--something the code emitted will probably do anyway.

A projection over the empty relation is the empty relation.

A join with the empty relation is the empty relation.

A union with the empty relation is the other operand.

I'm pretty sure the other operations can be constructed from those.

Brian

Quote from Dave Voorhis on October 14, 2019, 7:38 am

Type mismatches will throw an exception on the attempt to cast to Foo. A null parameter will throw relevant exceptions in the field comparison. Testing the parameter for equality with this should still return true per the equality test, but less efficiently. Etc.

I make no comment on which is better or worse.

Sub specie aeternitatis, either approach is reasonable.  In the Java context, throwing an exception breaks the contract of equals and would break hash tables, which assume they can call it on any two objects as long as they belong to the generic type of the key.

Quote from Brian S on October 14, 2019, 1:41 pm

Static type checking of scalar expressions should throw an error if the only common supertype is ALPHA because the subtypes are by definition disjoint, so such operations don't make sense.  But that is not the case for relation expressions.

Why, exactly?

The issue is that REL {A ALPHA, B ALPHA}{} is an element of types REL {A ODD, B EVEN} and REL {A EVEN, B ODD}, as well as REL {A INT, B CHAR}.  As a consequence, comparisons of relation expressions having the same set of attribute names must be allowed under the IM, and furthermore, must return true when both relation expressions are empty.

I don't buy that. Any element of type alpha must actually belong to one of its subtypes, and those are distinct. No value can be declared to be of type alpha, it becomes a value of type alpha by virtue of being declared as a value of some scalar type. Any value of the form REL {A any scalar,B any scalar}{} is actually a value of type REL {A alpha, B alpha} while retaining its original value non-alpha value, and those are separate values and test non-equal.

One of the arguments against there being a single empty relation, namely, the empty set, is that static type checking can be performed for relation expressions, but as the above example shows, that is not the case for relation expressions with the same set of attribute names under the IM.

So either we ditch the IM, or rethink the heading/body definition of relations.  I prefer the latter.  A tuple is a mapping from a set of attribute names onto a set of values.  A relation is a set of tuples having the same set of attribute names.  Then there is only one empty relation, namely, the empty set.  This only requires checking whether relation expressions are empty before making the comparison--something the code emitted will probably do anyway.

The rest of your arguments then fall away.

 

Andl - A New Database Language - andl.org
Quote from dandl on October 14, 2019, 11:12 pm
Quote from Brian S on October 14, 2019, 1:41 pm

Static type checking of scalar expressions should throw an error if the only common supertype is ALPHA because the subtypes are by definition disjoint, so such operations don't make sense.  But that is not the case for relation expressions.

Why, exactly?

The issue is that REL {A ALPHA, B ALPHA}{} is an element of types REL {A ODD, B EVEN} and REL {A EVEN, B ODD}, as well as REL {A INT, B CHAR}.  As a consequence, comparisons of relation expressions having the same set of attribute names must be allowed under the IM, and furthermore, must return true when both relation expressions are empty.

I don't buy that. Any element of type alpha must actually belong to one of its subtypes, and those are distinct. No value can be declared to be of type alpha, it becomes a value of type alpha by virtue of being declared as a value of some scalar type. Any value of the form REL {A any scalar,B any scalar}{} is actually a value of type REL {A alpha, B alpha} while retaining its original value non-alpha value, and those are separate values and test non-equal.

One of the arguments against there being a single empty relation, namely, the empty set, is that static type checking can be performed for relation expressions, but as the above example shows, that is not the case for relation expressions with the same set of attribute names under the IM.

So either we ditch the IM, or rethink the heading/body definition of relations.  I prefer the latter.  A tuple is a mapping from a set of attribute names onto a set of values.  A relation is a set of tuples having the same set of attribute names.  Then there is only one empty relation, namely, the empty set.  This only requires checking whether relation expressions are empty before making the comparison--something the code emitted will probably do anyway.

The rest of your arguments then fall away.

 

David,

Brian's issue is around the MST of an empty relation being OMEGA for all attributes, hence REL{A OMEGA, B OMEGA}.

The empty relation is a value of that type, so unlike OMEGA itself, types like REL {A OMEGA, B OMEGA} are not empty.

And per the rules as formulated an empty relation must be assignable to any variable whose type is a supertype of that empty relation's MST.  Meaning it must be assignable to, say, both a relvar of type {A PICTURE, B SOUND} as a relvar of type {A CHAR, B INT}.  (And this goes to show that the intersection type between these two relation types is not empty.)

These are plain facts, and it's not for you to choose whether you "buy that" or not.

Now since the intersection type is not empty, it could possibly occur at run-time that an equality test between two such relvars would succeed.  And you could (as Brian seems to do) derive from that that an equality test must be accepted at compile time.

The alternative is to observe that the scenario can only occur with empty relations which means that 'R1 == R2' could also be written 'R1 == PHI && R2 == PHI' and then stick with the 'sensible' rule even if means a somewhat inelegant hackish exception.

It has been discussed ad nauseam here (surprised ?) but I don't recall a firm conclusion coming out except "it's your D, you choose what you do".

Quote from Erwin on October 15, 2019, 6:50 am

...

It has been discussed ad nauseam here (surprised ?) but I don't recall a firm conclusion coming out except "it's your D, you choose what you do".

I thought that was the firm conclusion. :-)

I'm the forum administrator and lead developer of Rel. Email me at dave@armchair.mb.ca with the Subject 'TTM Forum'. Download Rel from https://reldb.org