A TTM Tuple is not a Record
Quote from dandl on May 2, 2020, 2:13 pmIf we look at the TTM type system in the light of modern compiled strongly typed languages, it's pretty obvious that the scalar types map easily:
- TTM system scalar types map directly into primitive system types like int, double and string
- TTM user-defined scalar types map directly into record types, with the proviso they need to behave as immutable value types. Struct in C++, struct in C#, the new record types in Java, no problem.
But what about TTM non-scalar types?
- TTM tuple types do not map to record types, they are different on almost every level.
- TTM relation types do not map to 'collection of record type', similar reasons.
Indeed, TTM tuple types are designed to be quite unlike TTM scalar types, so obviously they are not going to be map to the same thing. Why do we try to map tuple into records? Because historically we have either done a physical layout mapping (eg for an ISAM file) or used an ORM (for SQL). We know the mapping is imperfect, so we give them names like POJO/POCO as a reminder. None of this applies here.
So, start with the decision that a tuple is not a record and a relation is not a collection of records. Then what are they?
Required features for a TTM tuple type are:
- heading: set of attribute names
- values: list of typed values (one per attribute)
- selector per RM Pre 9 (argument is an ordered list of typed values)
- named getters: one per attribute (where needed)
- value type machinery: isequal() and hashcode()
Required features for a TTM relation type are:
- heading: set of attribute names
- key: set of attribute names
- body: set of tuple values
- selector per RM Pre 10 (argument is a set of typed tuples)
- value type machinery: isequal() and hashcode()
We can easily construct types with these features using the facilities of existing languages. I write code like this In sleep. If there's too much boilerplate, then a bit of code generation will help.
Of course they need to be supported by a set of library functions, as per TTM. No problem.
Here are tuple and relation types for Supplier data, in C#. Base classes are left as an exercise.
/// <summary> /// Generated relation type for S (suppliers) /// </summary> class RelS : RelationBase { RelS(IList<TupS> tuples) : base( new string[] { "SNo", "Sname", "Status", "City" }, new string[] { "SNo" }, new HashSet<object>(tuples.Cast<object>())) { } } /// <summary> /// Generated tuple type for S (suppliers) /// </summary> public class TupS : TupleBase { public string Sno { get { return (string)_values[0]; } } public string Sname { get { return (string)_values[1]; } } public int Status { get { return (int)_values[2]; } } public string City { get { return (string)_values[3]; } } public TupS(string Sno, string Sname, int Status, string City) : base( new string[] { "SNo", "Sname", "Status", "City" }, new object[] { Sno, Sname, Status, City }) { } } }
If we look at the TTM type system in the light of modern compiled strongly typed languages, it's pretty obvious that the scalar types map easily:
- TTM system scalar types map directly into primitive system types like int, double and string
- TTM user-defined scalar types map directly into record types, with the proviso they need to behave as immutable value types. Struct in C++, struct in C#, the new record types in Java, no problem.
But what about TTM non-scalar types?
- TTM tuple types do not map to record types, they are different on almost every level.
- TTM relation types do not map to 'collection of record type', similar reasons.
Indeed, TTM tuple types are designed to be quite unlike TTM scalar types, so obviously they are not going to be map to the same thing. Why do we try to map tuple into records? Because historically we have either done a physical layout mapping (eg for an ISAM file) or used an ORM (for SQL). We know the mapping is imperfect, so we give them names like POJO/POCO as a reminder. None of this applies here.
So, start with the decision that a tuple is not a record and a relation is not a collection of records. Then what are they?
Required features for a TTM tuple type are:
- heading: set of attribute names
- values: list of typed values (one per attribute)
- selector per RM Pre 9 (argument is an ordered list of typed values)
- named getters: one per attribute (where needed)
- value type machinery: isequal() and hashcode()
Required features for a TTM relation type are:
- heading: set of attribute names
- key: set of attribute names
- body: set of tuple values
- selector per RM Pre 10 (argument is a set of typed tuples)
- value type machinery: isequal() and hashcode()
We can easily construct types with these features using the facilities of existing languages. I write code like this In sleep. If there's too much boilerplate, then a bit of code generation will help.
Of course they need to be supported by a set of library functions, as per TTM. No problem.
Here are tuple and relation types for Supplier data, in C#. Base classes are left as an exercise.
/// <summary> /// Generated relation type for S (suppliers) /// </summary> class RelS : RelationBase { RelS(IList<TupS> tuples) : base( new string[] { "SNo", "Sname", "Status", "City" }, new string[] { "SNo" }, new HashSet<object>(tuples.Cast<object>())) { } } /// <summary> /// Generated tuple type for S (suppliers) /// </summary> public class TupS : TupleBase { public string Sno { get { return (string)_values[0]; } } public string Sname { get { return (string)_values[1]; } } public int Status { get { return (int)_values[2]; } } public string City { get { return (string)_values[3]; } } public TupS(string Sno, string Sname, int Status, string City) : base( new string[] { "SNo", "Sname", "Status", "City" }, new object[] { Sno, Sname, Status, City }) { } } }
Quote from Dave Voorhis on May 2, 2020, 5:29 pmI'm not clear what you're trying to achieve. If there are mechanisms for static type checking in your tuple and relation types, I'm not seeing them here. This looks like the sort of thing I'd expect to see in the internals of a D, not something a developer would use directly.
Of course, TTM tuple and relation types are defined internally in any D implementation, but you don't get native static type checking on them in C/C++/C#/Java. If you use code generation, then the code generator performs any static type checking on tuples and relations, but the language you're generating code from obviously isn't C/C++/C#/Java any more.
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
Or, you can do what I'm doing, which is dispense with TTM-style non-scalars but embody the intent of the relational model -- though not necessarily (all) its usual operators -- whilst retaining all the standard facilities of the popular general-purpose language in which it's implemented.
I'm not clear what you're trying to achieve. If there are mechanisms for static type checking in your tuple and relation types, I'm not seeing them here. This looks like the sort of thing I'd expect to see in the internals of a D, not something a developer would use directly.
Of course, TTM tuple and relation types are defined internally in any D implementation, but you don't get native static type checking on them in C/C++/C#/Java. If you use code generation, then the code generator performs any static type checking on tuples and relations, but the language you're generating code from obviously isn't C/C++/C#/Java any more.
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
Or, you can do what I'm doing, which is dispense with TTM-style non-scalars but embody the intent of the relational model -- though not necessarily (all) its usual operators -- whilst retaining all the standard facilities of the popular general-purpose language in which it's implemented.
Quote from dandl on May 3, 2020, 2:58 amQuote from Dave Voorhis on May 2, 2020, 5:29 pmI'm not clear what you're trying to achieve. If there are mechanisms for static type checking in your tuple and relation types, I'm not seeing them here. This looks like the sort of thing I'd expect to see in the internals of a D, not something a developer would use directly.
The static type checking is standard C#. These are individual relation and tuple types written or generated according to a set of rules and constrained by an abstract base class. What don't you understand? It should be familiar stuff.
Of course, TTM tuple and relation types are defined internally in any D implementation, but you don't get native static type checking on them in C/C++/C#/Java. If you use code generation, then the code generator performs any static type checking on tuples and relations, but the language you're generating code from obviously isn't C/C++/C#/Java any more.
There is no 'other language'. These classes are generated from data, provided by sources such as ODBC etc in exactly the same way as existing code generators already widely used.
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
Or, you can do what I'm doing, which is dispense with TTM-style non-scalars but embody the intent of the relational model -- though not necessarily (all) its usual operators -- whilst retaining all the standard facilities of the popular general-purpose language in which it's implemented.
I'm not interested in that path. I'm interested in showing how closely you can satisfy the requirements of TTM using the standard features of modern powerful GP programming languages, using standard programming techniques of the language.
As far as I can tell, this approach will satisfy over 95% of the TTM Pre and Pro requirements that relate to the D language and its type system. Some highlights:
- Complies with all of RM Pre 1-5 (except POSSREPS and IM)
- Complies with all of RM 6-13, 18 (see note), 19, 21 (except MA), 22, 26, all Pro (some not enforced)
- Complies with OO Pre 1,3,6 and all Pro
- Some prohibitions not enforced eg RM Pre 3a, 3d, Pro 7,8
- Database, catalog and transaction requirements depend on suitable libraries, not considered here
Note: the one you probably had in mind was RM Pre 18. This approach does support the 'usual operators of the RA', which is already a major achievement. The requirements of OO Pre 1 are also fully met, but it will need a bit of help to meet expectations as to type inference across relational operators. As it stands it is type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed. The information is available at compile time to do that type inference, so I shall be investigating how that might fit into a language extension. There is a mechanism in C# that might do the trick, but in Java, perhaps an annotation? (I really have no idea how that works.)
So apart from a quibble over whether type inference and checking across relational operators happens at compile time or run time, I think this meets the goal. I'll develop it a bit further and see where it leads.
Quote from Dave Voorhis on May 2, 2020, 5:29 pmI'm not clear what you're trying to achieve. If there are mechanisms for static type checking in your tuple and relation types, I'm not seeing them here. This looks like the sort of thing I'd expect to see in the internals of a D, not something a developer would use directly.
The static type checking is standard C#. These are individual relation and tuple types written or generated according to a set of rules and constrained by an abstract base class. What don't you understand? It should be familiar stuff.
Of course, TTM tuple and relation types are defined internally in any D implementation, but you don't get native static type checking on them in C/C++/C#/Java. If you use code generation, then the code generator performs any static type checking on tuples and relations, but the language you're generating code from obviously isn't C/C++/C#/Java any more.
There is no 'other language'. These classes are generated from data, provided by sources such as ODBC etc in exactly the same way as existing code generators already widely used.
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
Or, you can do what I'm doing, which is dispense with TTM-style non-scalars but embody the intent of the relational model -- though not necessarily (all) its usual operators -- whilst retaining all the standard facilities of the popular general-purpose language in which it's implemented.
I'm not interested in that path. I'm interested in showing how closely you can satisfy the requirements of TTM using the standard features of modern powerful GP programming languages, using standard programming techniques of the language.
As far as I can tell, this approach will satisfy over 95% of the TTM Pre and Pro requirements that relate to the D language and its type system. Some highlights:
- Complies with all of RM Pre 1-5 (except POSSREPS and IM)
- Complies with all of RM 6-13, 18 (see note), 19, 21 (except MA), 22, 26, all Pro (some not enforced)
- Complies with OO Pre 1,3,6 and all Pro
- Some prohibitions not enforced eg RM Pre 3a, 3d, Pro 7,8
- Database, catalog and transaction requirements depend on suitable libraries, not considered here
Note: the one you probably had in mind was RM Pre 18. This approach does support the 'usual operators of the RA', which is already a major achievement. The requirements of OO Pre 1 are also fully met, but it will need a bit of help to meet expectations as to type inference across relational operators. As it stands it is type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed. The information is available at compile time to do that type inference, so I shall be investigating how that might fit into a language extension. There is a mechanism in C# that might do the trick, but in Java, perhaps an annotation? (I really have no idea how that works.)
So apart from a quibble over whether type inference and checking across relational operators happens at compile time or run time, I think this meets the goal. I'll develop it a bit further and see where it leads.
Quote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
That's dynamic type checking; as I described above:
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
What you can't do is compile-time static type checking using the native type system of the general-purpose language. That would require either extending the semantics of the language to support TTM-style tuples and relations in languages that support such extensibility (to my knowledge, none of the usual popular general-purpose languages do), or using code generation externally via another language (which is how Rel works; is it not how Andl works?) or internally via annotations or other pre-processor mechanisms in languages that support them.
I didn't mean RM Pre 18. I meant OO Pre 1.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
That's dynamic type checking; as I described above:
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
What you can't do is compile-time static type checking using the native type system of the general-purpose language. That would require either extending the semantics of the language to support TTM-style tuples and relations in languages that support such extensibility (to my knowledge, none of the usual popular general-purpose languages do), or using code generation externally via another language (which is how Rel works; is it not how Andl works?) or internally via annotations or other pre-processor mechanisms in languages that support them.
Quote from Hugh on May 3, 2020, 2:24 pmQuote from dandl on May 2, 2020, 2:13 pm[snip]
Required features for a TTM tuple type are:
- heading: set of attribute names
- values: list of typed values (one per attribute)
- selector per RM Pre 9 (argument is an ordered list of typed values)
- named getters: one per attribute (where needed)
- value type machinery: isequal() and hashcode()
Required features for a TTM relation type are:
- heading: set of attribute names
- key: set of attribute names
- body: set of tuple values
- selector per RM Pre 10 (argument is a set of typed tuples)
- value type machinery: isequal() and hashcode()
[snip]
A heading has a type for each attribute as well as its name.
Hugh
Quote from dandl on May 2, 2020, 2:13 pm[snip]
Required features for a TTM tuple type are:
- heading: set of attribute names
- values: list of typed values (one per attribute)
- selector per RM Pre 9 (argument is an ordered list of typed values)
- named getters: one per attribute (where needed)
- value type machinery: isequal() and hashcode()
Required features for a TTM relation type are:
- heading: set of attribute names
- key: set of attribute names
- body: set of tuple values
- selector per RM Pre 10 (argument is a set of typed tuples)
- value type machinery: isequal() and hashcode()
[snip]
A heading has a type for each attribute as well as its name.
Hugh
Quote from dandl on May 3, 2020, 2:49 pmQuote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language. The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
What you can't do is compile-time static type checking using the native type system of the general-purpose language. That would require either extending the semantics of the language to support TTM-style tuples and relations in languages that support such extensibility (to my knowledge, none of the usual popular general-purpose languages do), or using code generation externally via another language (which is how Rel works; is it not how Andl works?) or internally via annotations or other pre-processor mechanisms in languages that support them.
But that's exactly what I set out to do: show that it's possible to satisfy the type system requirements of TTM by extending an existing GP language instead of creating a language from scratch. It turns out that, not only is this so, but I can get a whole lot closer than I expected. All that is left is one small part of the type checking that either has to be left to run time, or for which the compiler would have to be modified or augmented by some kind of helper. That seems to me a good outcome.
Incidentally, I draw your attention to the specific wording:
D shall support type inference for relation types, whereby the type of the result of evaluating an arbitrary relational expression shall be well defined and known to both the system and the user.
Nothing about compile time checking there. If the 'system' knows at runtime that would seem to be good enough.
Quote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language. The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
Alternatively, you can do dynamic type checking and create quite rich relational implementations in any general-purpose language.
What you can't do is compile-time static type checking using the native type system of the general-purpose language. That would require either extending the semantics of the language to support TTM-style tuples and relations in languages that support such extensibility (to my knowledge, none of the usual popular general-purpose languages do), or using code generation externally via another language (which is how Rel works; is it not how Andl works?) or internally via annotations or other pre-processor mechanisms in languages that support them.
But that's exactly what I set out to do: show that it's possible to satisfy the type system requirements of TTM by extending an existing GP language instead of creating a language from scratch. It turns out that, not only is this so, but I can get a whole lot closer than I expected. All that is left is one small part of the type checking that either has to be left to run time, or for which the compiler would have to be modified or augmented by some kind of helper. That seems to me a good outcome.
Incidentally, I draw your attention to the specific wording:
D shall support type inference for relation types, whereby the type of the result of evaluating an arbitrary relational expression shall be well defined and known to both the system and the user.
Nothing about compile time checking there. If the 'system' knows at runtime that would seem to be good enough.
Quote from Dave Voorhis on May 3, 2020, 3:30 pmQuote from dandl on May 3, 2020, 2:49 pmQuote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Given C# representations of tuple types T1 and tuple T2, where do you statically determine whether they are type-compatible or not?
For C# to be considered TTM-compliant under OO Pre #1, I would expect the C# representation of TUPLE {x CHAR, y INT} to be type-mismatch with the C# representation of TUPLE {x INT, y CHAR} and type-equivalent with the C# representation of TUPLE {y INT, x CHAR} and for these to be determined statically by the compiler.
Of course, it's quite straightforward to perform the type-checking dynamically at run-time -- say, before a relational expression is evaluated -- but that doesn't appear to be what OO Pre #1 has in mind.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language.
Ah, that's interesting. The core of Rel is not type-system dependent, being based entirely on two interfaces, Type and Value. Type defines a method
boolean canAccept(Type t)
, which returns true for any type t that is type-compatible with this Type. Value defines operators for a corresponding Type. This allows Rel to use any type system as long its types and values can implement Type and Value, for which the Java type system would (and essentially does) fit nicely with some simple wrappers. Indeed, CHAR, BOOLEAN, RATIONAL and INTEGER are simply wrappers around Java's String, boolean, double, and long.But, as noted, the type checking happens at Java run-time -- which is Rel's compile-time -- because the core language code needs to invoke
canAccept(Type t)
.The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
Sorry, I'm not following you here, unless we're talking about the same need to statically check types at run-time.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
It should be trivial to prevent a relvar variable holding a tuple value, assuming the former is declared as (say)
class Relvar
and the latterclass Tuple
. How do you ensure that a C# representation of Tuple1 where Tuple1 isTUPLE {x INT, y CHAR}
is type-compatible with Tuple2 where Tuple2 isTUPLE {y CHAR, x INT}
?
Quote from dandl on May 3, 2020, 2:49 pmQuote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Given C# representations of tuple types T1 and tuple T2, where do you statically determine whether they are type-compatible or not?
For C# to be considered TTM-compliant under OO Pre #1, I would expect the C# representation of TUPLE {x CHAR, y INT} to be type-mismatch with the C# representation of TUPLE {x INT, y CHAR} and type-equivalent with the C# representation of TUPLE {y INT, x CHAR} and for these to be determined statically by the compiler.
Of course, it's quite straightforward to perform the type-checking dynamically at run-time -- say, before a relational expression is evaluated -- but that doesn't appear to be what OO Pre #1 has in mind.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language.
Ah, that's interesting. The core of Rel is not type-system dependent, being based entirely on two interfaces, Type and Value. Type defines a method boolean canAccept(Type t)
, which returns true for any type t that is type-compatible with this Type. Value defines operators for a corresponding Type. This allows Rel to use any type system as long its types and values can implement Type and Value, for which the Java type system would (and essentially does) fit nicely with some simple wrappers. Indeed, CHAR, BOOLEAN, RATIONAL and INTEGER are simply wrappers around Java's String, boolean, double, and long.
But, as noted, the type checking happens at Java run-time -- which is Rel's compile-time -- because the core language code needs to invoke canAccept(Type t)
.
The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
Sorry, I'm not following you here, unless we're talking about the same need to statically check types at run-time.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
It should be trivial to prevent a relvar variable holding a tuple value, assuming the former is declared as (say) class Relvar
and the latter class Tuple
. How do you ensure that a C# representation of Tuple1 where Tuple1 is TUPLE {x INT, y CHAR}
is type-compatible with Tuple2 where Tuple2 is TUPLE {y CHAR, x INT}
?
Quote from dandl on May 4, 2020, 12:06 amQuote from Hugh on May 3, 2020, 2:24 pmQuote from dandl on May 2, 2020, 2:13 pm[snip]
Required features for a TTM tuple type are:
- heading: set of attribute names
- values: list of typed values (one per attribute)
- selector per RM Pre 9 (argument is an ordered list of typed values)
- named getters: one per attribute (where needed)
- value type machinery: isequal() and hashcode()
Required features for a TTM relation type are:
- heading: set of attribute names
- key: set of attribute names
- body: set of tuple values
- selector per RM Pre 10 (argument is a set of typed tuples)
- value type machinery: isequal() and hashcode()
[snip]
A heading has a type for each attribute as well as its name.
Hugh
Just so. Here is the latest version of my C# code.
public class TupS : TupleBase { public string Sno { get { return (string)_values[0]; } } public string Sname { get { return (string)_values[1]; } } public int Status { get { return (int)_values[2]; } } public string City { get { return (string)_values[3]; } } static TupS() { _heading = new string[] { "SNo", "Sname", "Status", "City" }; } public TupS(string Sno, string Sname, int Status, string City) : base( new object[] { Sno, Sname, Status, City }) { }The attribute types are defined by the constructor and the getters.
[This code could be generated by a type definition similar to Tutorial D, but there is really no need.]
Quote from Hugh on May 3, 2020, 2:24 pmQuote from dandl on May 2, 2020, 2:13 pm[snip]
Required features for a TTM tuple type are:
- heading: set of attribute names
- values: list of typed values (one per attribute)
- selector per RM Pre 9 (argument is an ordered list of typed values)
- named getters: one per attribute (where needed)
- value type machinery: isequal() and hashcode()
Required features for a TTM relation type are:
- heading: set of attribute names
- key: set of attribute names
- body: set of tuple values
- selector per RM Pre 10 (argument is a set of typed tuples)
- value type machinery: isequal() and hashcode()
[snip]
A heading has a type for each attribute as well as its name.
Hugh
Just so. Here is the latest version of my C# code.
public class TupS : TupleBase { public string Sno { get { return (string)_values[0]; } } public string Sname { get { return (string)_values[1]; } } public int Status { get { return (int)_values[2]; } } public string City { get { return (string)_values[3]; } } static TupS() { _heading = new string[] { "SNo", "Sname", "Status", "City" }; } public TupS(string Sno, string Sname, int Status, string City) : base( new object[] { Sno, Sname, Status, City }) { }
The attribute types are defined by the constructor and the getters.
[This code could be generated by a type definition similar to Tutorial D, but there is really no need.]
Quote from dandl on May 4, 2020, 1:19 amQuote from Dave Voorhis on May 3, 2020, 3:30 pmQuote from dandl on May 3, 2020, 2:49 pmQuote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Given C# representations of tuple types T1 and tuple T2, where do you statically determine whether they are type-compatible or not?
They have separate definitions. TupS is one type, TupP is a different type.
What do you mean by 'compatible'? That's not a term used in TTM.
For C# to be considered TTM-compliant under OO Pre #1, I would expect the C# representation of TUPLE {x CHAR, y INT} to be type-mismatch with the C# representation of TUPLE {x INT, y CHAR} and type-equivalent with the C# representation of TUPLE {y INT, x CHAR} and for these to be determined statically by the compiler.
Of course, it's quite straightforward to perform the type-checking dynamically at run-time -- say, before a relational expression is evaluated -- but that doesn't appear to be what OO Pre #1 has in mind.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language.
Ah, that's interesting. The core of Rel is not type-system dependent, being based entirely on two interfaces, Type and Value. Type defines a method
boolean canAccept(Type t)
, which returns true for any type t that is type-compatible with this Type. Value defines operators for a corresponding Type. This allows Rel to use any type system as long its types and values can implement Type and Value, for which the Java type system would (and essentially does) fit nicely with some simple wrappers. Indeed, CHAR, BOOLEAN, RATIONAL and INTEGER are simply wrappers around Java's String, boolean, double, and long.But, as noted, the type checking happens at Java run-time -- which is Rel's compile-time -- because the core language code needs to invoke
canAccept(Type t)
.I guess Andl is built differently. It has a full blown type system, and every value is an instance of
TypedValue
. Types are instances ofDataType
. I'd never built a full type system from scratch before, so that was a major goal.The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
Sorry, I'm not following you here, unless we're talking about the same need to statically check types at run-time.
All types are statically checked.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
It should be trivial to prevent a relvar variable holding a tuple value, assuming the former is declared as (say)
class Relvar
and the latterclass Tuple
. How do you ensure that a C# representation of Tuple1 where Tuple1 isTUPLE {x INT, y CHAR}
is type-compatible with Tuple2 where Tuple2 isTUPLE {y CHAR, x INT}
?You're still thinking records, and these are not records. That question cannot arise. It has no meaning.
Let me try to clarify what I claim.
- D/GP is a way of representing the TTM D type system in a modern general purpose strongly typed programming language.
- As described, it fully complies with TTM (excluding Possreps and the IM).
- The means used are 100% native code, using the native type system to ensure type safety.
- There are some rules about how TTM scalar and non-scalar types are constructed and used. Failure to comply is 'undefined behaviour' (see note).
- The RA requirements in RM Pre 18 as to 'type inference' are satisfied (see note).
- There are some other rules in TTM (RM Pre 3a, 3d, Pro 7,8); failure to comply with these is 'undefined behaviour' (see note).
A GP language will inevitably allow a wide range of behaviours, some of which violate TTM requirements. You can write complying code, but the compiler isn't going to help you. So you have 3 choices:
- Leave it as 'undefined behaviour' like C/C++, and let the programmer deal with compliance issues.
- Enforce the rules at run time, as type errors, assertions or whatever.
- Modify or extend the compiler (not the language) to add TTM compliance checking.
For example (as per your question above), one of the rules is that you are only allowed to declare one tuple type with any given heading. Given that a heading is a set of attribute names (and associated types), declaring Tuple2 with the same heading as Tuple1 was not allowed. Every tuple with that heading is a Tuple1, and type safety is guaranteed. This complies with RM Pre 6.
I would expect that early implementations of a D/GP would enforce chosen rules at runtime in a variety of ways, possibly including reflection. If it turns out to be a good idea, a modified compiler with a 'TTM mode' and/or
@TupleType
annotations would be able to enforce the rules at compile time.If I've missed something please point it out, but it all looks solid to me.
Quote from Dave Voorhis on May 3, 2020, 3:30 pmQuote from dandl on May 3, 2020, 2:49 pmQuote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Given C# representations of tuple types T1 and tuple T2, where do you statically determine whether they are type-compatible or not?
They have separate definitions. TupS is one type, TupP is a different type.
What do you mean by 'compatible'? That's not a term used in TTM.
For C# to be considered TTM-compliant under OO Pre #1, I would expect the C# representation of TUPLE {x CHAR, y INT} to be type-mismatch with the C# representation of TUPLE {x INT, y CHAR} and type-equivalent with the C# representation of TUPLE {y INT, x CHAR} and for these to be determined statically by the compiler.
Of course, it's quite straightforward to perform the type-checking dynamically at run-time -- say, before a relational expression is evaluated -- but that doesn't appear to be what OO Pre #1 has in mind.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language.
Ah, that's interesting. The core of Rel is not type-system dependent, being based entirely on two interfaces, Type and Value. Type defines a method
boolean canAccept(Type t)
, which returns true for any type t that is type-compatible with this Type. Value defines operators for a corresponding Type. This allows Rel to use any type system as long its types and values can implement Type and Value, for which the Java type system would (and essentially does) fit nicely with some simple wrappers. Indeed, CHAR, BOOLEAN, RATIONAL and INTEGER are simply wrappers around Java's String, boolean, double, and long.But, as noted, the type checking happens at Java run-time -- which is Rel's compile-time -- because the core language code needs to invoke
canAccept(Type t)
.
I guess Andl is built differently. It has a full blown type system, and every value is an instance of TypedValue
. Types are instances of DataType
. I'd never built a full type system from scratch before, so that was a major goal.
The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
Sorry, I'm not following you here, unless we're talking about the same need to statically check types at run-time.
All types are statically checked.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
It should be trivial to prevent a relvar variable holding a tuple value, assuming the former is declared as (say)
class Relvar
and the latterclass Tuple
. How do you ensure that a C# representation of Tuple1 where Tuple1 isTUPLE {x INT, y CHAR}
is type-compatible with Tuple2 where Tuple2 isTUPLE {y CHAR, x INT}
?
You're still thinking records, and these are not records. That question cannot arise. It has no meaning.
Let me try to clarify what I claim.
- D/GP is a way of representing the TTM D type system in a modern general purpose strongly typed programming language.
- As described, it fully complies with TTM (excluding Possreps and the IM).
- The means used are 100% native code, using the native type system to ensure type safety.
- There are some rules about how TTM scalar and non-scalar types are constructed and used. Failure to comply is 'undefined behaviour' (see note).
- The RA requirements in RM Pre 18 as to 'type inference' are satisfied (see note).
- There are some other rules in TTM (RM Pre 3a, 3d, Pro 7,8); failure to comply with these is 'undefined behaviour' (see note).
A GP language will inevitably allow a wide range of behaviours, some of which violate TTM requirements. You can write complying code, but the compiler isn't going to help you. So you have 3 choices:
- Leave it as 'undefined behaviour' like C/C++, and let the programmer deal with compliance issues.
- Enforce the rules at run time, as type errors, assertions or whatever.
- Modify or extend the compiler (not the language) to add TTM compliance checking.
For example (as per your question above), one of the rules is that you are only allowed to declare one tuple type with any given heading. Given that a heading is a set of attribute names (and associated types), declaring Tuple2 with the same heading as Tuple1 was not allowed. Every tuple with that heading is a Tuple1, and type safety is guaranteed. This complies with RM Pre 6.
I would expect that early implementations of a D/GP would enforce chosen rules at runtime in a variety of ways, possibly including reflection. If it turns out to be a good idea, a modified compiler with a 'TTM mode' and/or @TupleType
annotations would be able to enforce the rules at compile time.
If I've missed something please point it out, but it all looks solid to me.
Quote from Dave Voorhis on May 4, 2020, 9:54 amQuote from dandl on May 4, 2020, 1:19 amQuote from Dave Voorhis on May 3, 2020, 3:30 pmQuote from dandl on May 3, 2020, 2:49 pmQuote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Given C# representations of tuple types T1 and tuple T2, where do you statically determine whether they are type-compatible or not?
They have separate definitions. TupS is one type, TupP is a different type.
What do you mean by 'compatible'? That's not a term used in TTM.
It's conventional terminology. Loosely, type-compatibility is the opposite of a type mismatch. In TTM terms, T1 is type-compatible with type T2 if values of T1 can be assigned to a variable or parameter of type T2.
If TupS is one type and TupP is a different type, assuming TupS is the C# equivalent to
TUPLE {x INT, y CHAR}
and TupP is the C# equivalent toTUPLE {y CHAR, x INT}
, are they type-compatible (as I would expect them to be per TTM's structural typing)?Or are they type-incompatible (as I would expect them to be per C# nominative typing)?
Or do you perform an explicit conversion from one to the other to essentially convert C#'s nominative typing to structural typing?
Or do you do something else?
For C# to be considered TTM-compliant under OO Pre #1, I would expect the C# representation of TUPLE {x CHAR, y INT} to be type-mismatch with the C# representation of TUPLE {x INT, y CHAR} and type-equivalent with the C# representation of TUPLE {y INT, x CHAR} and for these to be determined statically by the compiler.
Of course, it's quite straightforward to perform the type-checking dynamically at run-time -- say, before a relational expression is evaluated -- but that doesn't appear to be what OO Pre #1 has in mind.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language.
Ah, that's interesting. The core of Rel is not type-system dependent, being based entirely on two interfaces, Type and Value. Type defines a method
boolean canAccept(Type t)
, which returns true for any type t that is type-compatible with this Type. Value defines operators for a corresponding Type. This allows Rel to use any type system as long its types and values can implement Type and Value, for which the Java type system would (and essentially does) fit nicely with some simple wrappers. Indeed, CHAR, BOOLEAN, RATIONAL and INTEGER are simply wrappers around Java's String, boolean, double, and long.But, as noted, the type checking happens at Java run-time -- which is Rel's compile-time -- because the core language code needs to invoke
canAccept(Type t)
.I guess Andl is built differently. It has a full blown type system, and every value is an instance of
TypedValue
. Types are instances ofDataType
. I'd never built a full type system from scratch before, so that was a major goal.The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
Sorry, I'm not following you here, unless we're talking about the same need to statically check types at run-time.
All types are statically checked.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
It should be trivial to prevent a relvar variable holding a tuple value, assuming the former is declared as (say)
class Relvar
and the latterclass Tuple
. How do you ensure that a C# representation of Tuple1 where Tuple1 isTUPLE {x INT, y CHAR}
is type-compatible with Tuple2 where Tuple2 isTUPLE {y CHAR, x INT}
?You're still thinking records, and these are not records. That question cannot arise. It has no meaning.
No, I'm not "thinking records", and I'm not sure what that means.
I'm thinking of TTM's tuples (and relations) and its use of structural typing as opposed to C#'s classes and structs and its use of nominative typing. How are you reconciling these distinct approaches?
Let me try to clarify what I claim.
- D/GP is a way of representing the TTM D type system in a modern general purpose strongly typed programming language.
- As described, it fully complies with TTM (excluding Possreps and the IM).
- The means used are 100% native code, using the native type system to ensure type safety.
- There are some rules about how TTM scalar and non-scalar types are constructed and used. Failure to comply is 'undefined behaviour' (see note).
- The RA requirements in RM Pre 18 as to 'type inference' are satisfied (see note).
- There are some other rules in TTM (RM Pre 3a, 3d, Pro 7,8); failure to comply with these is 'undefined behaviour' (see note).
A GP language will inevitably allow a wide range of behaviours, some of which violate TTM requirements. You can write complying code, but the compiler isn't going to help you. So you have 3 choices:
- Leave it as 'undefined behaviour' like C/C++, and let the programmer deal with compliance issues.
- Enforce the rules at run time, as type errors, assertions or whatever.
- Modify or extend the compiler (not the language) to add TTM compliance checking.
For example (as per your question above), one of the rules is that you are only allowed to declare one tuple type with any given heading. Given that a heading is a set of attribute names (and associated types), declaring Tuple2 with the same heading as Tuple1 was not allowed. Every tuple with that heading is a Tuple1, and type safety is guaranteed. This complies with RM Pre 6.
I would expect that early implementations of a D/GP would enforce chosen rules at runtime in a variety of ways, possibly including reflection. If it turns out to be a good idea, a modified compiler with a 'TTM mode' and/or
@TupleType
annotations would be able to enforce the rules at compile time.If I've missed something please point it out, but it all looks solid to me.
So if I understand you correctly, the C# equivalent of TUPLE {x INT, y CHAR} and the C# equivalent of TUPLE {y CHAR, x INT} are treated as distinct types. In other words, you adhere to C#'s nominative typing and dispense with TTM's structural typing?
Quote from dandl on May 4, 2020, 1:19 amQuote from Dave Voorhis on May 3, 2020, 3:30 pmQuote from dandl on May 3, 2020, 2:49 pmQuote from Dave Voorhis on May 3, 2020, 7:14 amI didn't mean RM Pre 18. I meant OO Pre 1.
But the solution I propose completely satisfies OO Pre 1, to exactly the same extent as C#. Every expression, variable, attribute, component, argument and operator in the set of types I described earlier is checked by the compiler for safety using the native features of the C# compiler. What do you think is missing?
Given C# representations of tuple types T1 and tuple T2, where do you statically determine whether they are type-compatible or not?
They have separate definitions. TupS is one type, TupP is a different type.
What do you mean by 'compatible'? That's not a term used in TTM.
It's conventional terminology. Loosely, type-compatibility is the opposite of a type mismatch. In TTM terms, T1 is type-compatible with type T2 if values of T1 can be assigned to a variable or parameter of type T2.
If TupS is one type and TupP is a different type, assuming TupS is the C# equivalent to TUPLE {x INT, y CHAR}
and TupP is the C# equivalent to TUPLE {y CHAR, x INT}
, are they type-compatible (as I would expect them to be per TTM's structural typing)?
Or are they type-incompatible (as I would expect them to be per C# nominative typing)?
Or do you perform an explicit conversion from one to the other to essentially convert C#'s nominative typing to structural typing?
Or do you do something else?
For C# to be considered TTM-compliant under OO Pre #1, I would expect the C# representation of TUPLE {x CHAR, y INT} to be type-mismatch with the C# representation of TUPLE {x INT, y CHAR} and type-equivalent with the C# representation of TUPLE {y INT, x CHAR} and for these to be determined statically by the compiler.
Of course, it's quite straightforward to perform the type-checking dynamically at run-time -- say, before a relational expression is evaluated -- but that doesn't appear to be what OO Pre #1 has in mind.
Without it, what you describe is straightforward. Indeed, I would assume what you're attempting has already been done by you. Don't you already have tuples, relations, and a relational algebra implemented in the core engine of Andl?
Couldn't you use that core engine in C# without the Andl parser?
Or in your own words, use "the standard features of modern powerful GP programming languages, using standard programming techniques of the language"?
Yes, I can use parts of it but Andl devotes a large amount of code to constructing a type system, and the core engine is built to work with that. The entire project is vastly simpler when all the types are native to the implementing language.
Ah, that's interesting. The core of Rel is not type-system dependent, being based entirely on two interfaces, Type and Value. Type defines a method
boolean canAccept(Type t)
, which returns true for any type t that is type-compatible with this Type. Value defines operators for a corresponding Type. This allows Rel to use any type system as long its types and values can implement Type and Value, for which the Java type system would (and essentially does) fit nicely with some simple wrappers. Indeed, CHAR, BOOLEAN, RATIONAL and INTEGER are simply wrappers around Java's String, boolean, double, and long.But, as noted, the type checking happens at Java run-time -- which is Rel's compile-time -- because the core language code needs to invoke
canAccept(Type t)
.I guess Andl is built differently. It has a full blown type system, and every value is an instance of
TypedValue
. Types are instances ofDataType
. I'd never built a full type system from scratch before, so that was a major goal.The bits I need are actually in Andl.NET, and I expect to adapt those.
I certainly can do so with the core "relational engine" from Rel. I can use it without Rel's Tutorial D parser. I would expect any D to be similar in that respect.
The only problem is that the popular general-purpose programming languages constrain you to -- again using your words -- being "type safe at runtime, and that type safety is enforced early, as soon as a relational expression is evaluated, before any data is accessed."
No, you missed an important qualification. The solution I present is intrinsically type safe, as Java or C# are type safe. It satisfies OO Pre 1. However, there is exactly one small part of the solution arising out of RM Pre 18 where the types are known at compile time but the existing compiler cannot use that information. This is analogous to Java and its generics: the JVM has not changed but the compiler for generics is now smarter. I can see several ways to resolve the problem, but for now that is a gap, filled at runtime.
Sorry, I'm not following you here, unless we're talking about the same need to statically check types at run-time.
All types are statically checked.
That's dynamic type checking; as I described above:
No, it really isn't. You can call it type erasure like Java generics, but there is no possibility (for example) of a relvar variable holding a tuple value, or an S relvar being updated by a P expression. The type system won't allow it.
It should be trivial to prevent a relvar variable holding a tuple value, assuming the former is declared as (say)
class Relvar
and the latterclass Tuple
. How do you ensure that a C# representation of Tuple1 where Tuple1 isTUPLE {x INT, y CHAR}
is type-compatible with Tuple2 where Tuple2 isTUPLE {y CHAR, x INT}
?You're still thinking records, and these are not records. That question cannot arise. It has no meaning.
No, I'm not "thinking records", and I'm not sure what that means.
I'm thinking of TTM's tuples (and relations) and its use of structural typing as opposed to C#'s classes and structs and its use of nominative typing. How are you reconciling these distinct approaches?
Let me try to clarify what I claim.
- D/GP is a way of representing the TTM D type system in a modern general purpose strongly typed programming language.
- As described, it fully complies with TTM (excluding Possreps and the IM).
- The means used are 100% native code, using the native type system to ensure type safety.
- There are some rules about how TTM scalar and non-scalar types are constructed and used. Failure to comply is 'undefined behaviour' (see note).
- The RA requirements in RM Pre 18 as to 'type inference' are satisfied (see note).
- There are some other rules in TTM (RM Pre 3a, 3d, Pro 7,8); failure to comply with these is 'undefined behaviour' (see note).
A GP language will inevitably allow a wide range of behaviours, some of which violate TTM requirements. You can write complying code, but the compiler isn't going to help you. So you have 3 choices:
- Leave it as 'undefined behaviour' like C/C++, and let the programmer deal with compliance issues.
- Enforce the rules at run time, as type errors, assertions or whatever.
- Modify or extend the compiler (not the language) to add TTM compliance checking.
For example (as per your question above), one of the rules is that you are only allowed to declare one tuple type with any given heading. Given that a heading is a set of attribute names (and associated types), declaring Tuple2 with the same heading as Tuple1 was not allowed. Every tuple with that heading is a Tuple1, and type safety is guaranteed. This complies with RM Pre 6.
I would expect that early implementations of a D/GP would enforce chosen rules at runtime in a variety of ways, possibly including reflection. If it turns out to be a good idea, a modified compiler with a 'TTM mode' and/or
@TupleType
annotations would be able to enforce the rules at compile time.If I've missed something please point it out, but it all looks solid to me.
So if I understand you correctly, the C# equivalent of TUPLE {x INT, y CHAR} and the C# equivalent of TUPLE {y CHAR, x INT} are treated as distinct types. In other words, you adhere to C#'s nominative typing and dispense with TTM's structural typing?