I've been giving some more thought to FOR, ORDER, and the switch that can make these available or unavailable.
I'm now inclined to implement FOR and ORDER, but make them
always available. I.e.,
not provide a switch to turn them off.
My reasoning is this:
- Pragmatic argument: Use of a switch means Author A (with switch 'on') can produce code or whole scripts that Author B (with switch 'off') cannot run.
- Theoretical argument: We can presume that the reason for evaluating any relational expression is to produce a relation for some purpose. Inevitably, that purpose will involve iterating the tuples in the relation. If we don't intend to iterate the tuples in a relation, then a relation is merely a meaningless "black box"; we might as well not have obtained it in the first place.
- If Tutorial D is a database sublanguage, then we can assume that tuple iteration will typically occur on the "client side", i.e., somewhere that is of no concern to the database sublanguage. As such, ARRAY would be sufficient for the rare tuple iterations that will be needed, and the absence of FOR would encourage users to employ relational operators rather than ad-hoc iteration.
- However, if Tutorial D is a general-purpose programming language -- which, I would argue, it is -- then there is no "client side". The result of every relational expression will involve tuple iteration. Of course, some tuple iteration will be invisible to the end-user; it will happen inside some pre-written library operator. However, another Tutorial D user had to write that library operator, and had to use tuple iteration to do it. If no pre-written library operator exists, the Tutorial D end-user will have to explicitly iterate tuples. As such, Tutorial D users inevitably deal with tuple iteration. Therefore, it makes sense to unconditionally facilitate tuple iteration rather than deprecate it.
- Pedagogical argument: As iterating the tuples of a relation is inevitable if Tutorial D is a general-purpose programming language (see "Theoretical argument"), it makes sense to provide reasonable facilities for tuple iteration, and to teach when and where it is, or isn't, appropriate.
As an example, the Rel backup script -- DatabaseToScript.d -- could make use of FOR and ORDER. It currently looks like this:
Code: Select all
// Version 0.3.1 Database Backup Script
BEGIN TRANSACTION;
WRITELN "/*** Rel Database Backup ***/";
WRITELN;
WRITELN "// Created in Rel Version " || (ver FROM TUPLE FROM (EXTEND sys.Version ADD (major || "." || minor || "." || revision || " " || release AS ver)));
WRITELN "// Using DatabaseToScript version 0.3.1";
WRITELN;
WRITELN "BEGIN TRANSACTION;";
WRITELN;
VAR schema ARRAY SAME_TYPE_AS (TUPLE FROM EXTEND sys.Catalog {Name, Owner, CreationSequence, Definition, isVirtual} ADD ("var" AS objectType));
LOAD schema FROM
UNION {
EXTEND sys.Catalog {Name, Owner, CreationSequence, Definition, isVirtual} WHERE Owner <> "Rel" ADD ("var" AS objectType),
EXTEND (sys.Operators WHERE CreatedByType="") {Name, Owner, CreationSequence, Definition} WHERE Owner <> "Rel" ADD ("operator" AS objectType, false AS isVirtual),
EXTEND sys.Types {Name, Owner, CreationSequence, Definition} WHERE Owner <> "Rel" ADD ("type" AS objectType, false AS isVirtual)
}
ORDER (ASC CreationSequence);
VAR i INTEGER;
DO i := 0 TO COUNT(schema) - 1;
BEGIN;
VAR object INIT (schema[i]);
VAR objectType INIT (objectType FROM object);
VAR name INIT (Name FROM object);
VAR definition INIT (Definition FROM object);
CASE;
WHEN objectType = "var" THEN
BEGIN;
WRITELN "ANNOUNCE 'VAR " || name || "';";
WRITELN "VAR " || name || " " || definition || ";";
VAR isRealRelvar INIT (NOT (isVirtual FROM object));
IF isRealRelvar THEN
BEGIN;
EXECUTE "IF COUNT(" || name || ") > 0 THEN BEGIN; WRITE '" || name || " := '; OUTPUT " || name || "; WRITELN ';'; END; END IF;";
END;
END IF;
END;
WHEN objectType = "operator" THEN
BEGIN;
WRITELN "ANNOUNCE 'OPERATOR " || name || "';";
WRITELN definition;
END;
WHEN objectType = "type" THEN
BEGIN;
WRITELN "ANNOUNCE 'TYPE " || name || "';";
WRITELN definition;
END;
END CASE;
WRITELN;
END;
END DO;
LOAD schema FROM
EXTEND sys.Constraints {Name, Owner, CreationSequence, Definition} WHERE Owner <> "Rel" ADD ("constraint" AS objectType, false AS isVirtual)
ORDER (ASC CreationSequence);
DO i := 0 TO COUNT(schema) - 1;
BEGIN;
object := schema[i];
name := Name FROM object;
definition := Definition FROM object;
WRITELN "ANNOUNCE 'CONSTRAINT " || name || "';";
WRITELN "CONSTRAINT " || name || " " || definition || ";";
WRITELN;
END;
END DO;
WRITELN "COMMIT;";
WRITELN;
WRITELN "/*** End of Rel Database Backup ***/";
WRITELN "ANNOUNCE 'End of Script.';";
Note, in particular, the rather inelegant use of temporary variables to extract the relevant attributes from the ARRAY.
With the use of FOR and ORDER (and some other simplifications that aren't relevant here), it becomes the following:
Code: Select all
// Version 0.3.2 Database Backup Script
WRITELN "/*** Rel Database Backup ***/";
WRITELN;
WRITELN "// Created in Rel Version " || (ver FROM TUPLE FROM (EXTEND sys.Version ADD (major || "." || minor || "." || revision || " " || release AS ver)));
WRITELN "// Using DatabaseToScript version 0.3.2";
WRITELN;
WRITELN "BEGIN TRANSACTION;";
WRITELN;
BEGIN TRANSACTION;
FOR
UNION {
EXTEND sys.Catalog {Name, Owner, CreationSequence, Definition, isVirtual} WHERE Owner <> "Rel" ADD (0 AS ProcessSequence, "var" AS objectType),
EXTEND (sys.Operators WHERE CreatedByType="") {Name, Owner, CreationSequence, Definition} WHERE Owner <> "Rel" ADD (0 AS ProcessSequence, "operator" AS objectType, false AS isVirtual),
EXTEND sys.Types {Name, Owner, CreationSequence, Definition} WHERE Owner <> "Rel" ADD (0 AS ProcessSequence, "type" AS objectType, false AS isVirtual),
EXTEND sys.Constraints {Name, Owner, CreationSequence, Definition} WHERE Owner <> "Rel" ADD (1 AS ProcessSequence, "constraint" AS objectType, false AS isVirtual)
} ORDER (ASC ProcessSequence, ASC CreationSequence);
BEGIN;
WRITELN "ANNOUNCE '" || objectType || " " || Name || "';";
CASE;
WHEN objectType = "var" THEN
BEGIN;
WRITELN "VAR " || Name || " " || Definition || ";";
IF NOT isVirtual THEN
EXECUTE "IF COUNT(" || Name || ") > 0 THEN BEGIN; WRITE '" || Name || " := '; OUTPUT " || Name || "; WRITELN ';'; END; END IF;";
END IF;
END;
WHEN objectType = "constraint" THEN WRITELN "CONSTRAINT " || Name || " " || Definition || ";";
ELSE WRITELN Definition;
END CASE;
WRITELN;
END;
END FOR;
COMMIT;
WRITELN "COMMIT;";
WRITELN;
WRITELN "/*** End of Rel Database Backup ***/";
WRITELN "ANNOUNCE 'End of Script.';";
The above is considerably simpler and more elegant than the original script. If ORDER and FOR can be switched off, I can only provide the first version -- otherwise users who turn off FOR and ORDER can't make backups. I
could provide two versions, of course, but this introduces unnecessary complexity and the need to maintain two separate but functionally equivalent pieces of code.
The script also provides a good example of the appropriate use of FOR and ORDER; one in which tuple iteration is necessary and unavoidable. Since there are an infinite number of cases where tuple iteration will be necessary and unavoidable in a general-purpose programming language -- i.e., on the result of
every relational expression evaluation for which a pre-existing operator doesn't do what we need -- FOR and ORDER should be unconditionally available. If this means students mis-use the facility and employ FOR where appropriate relational operators should be used instead... Well, for them I have plenty of 'D' and 'F' grades to give out!