The Forum for Discussion about The Third Manifesto and Related Matters

You need to log in to create posts and topics.

Possreps, Objects, Mutability, Immutability, and Developing Non-database Applications.

Quote from dandl on June 19, 2019, 2:16 am
Quote from Dave Voorhis on June 18, 2019, 8:43 pm
Quote from dandl on June 18, 2019, 11:27 am

I'm currently writing a game based on a hex grid, with tiles and loops. I have Point, Rect, Hex, Layout, Orientation, Piece and Arc as value types, but Tile, Grid, Board, Game and most of the others are implemented as mutable classes. Take TileModel for example, which provides the data to be consumed by TileView and displayed on the screen. TileModel has a property method called IsChanged which will be true if the model has changed since it was last displayed, meaning that the view needs to be updated. TileView keeps a reference to TileModel which it can query as needed. It's a familiar OO pattern.

So given that my purpose is to implement a model and a view on that model in the way set out here, how would you express that in the TTM type system? I think I might be able to find a way, but I can't see that the approach would have much in common with the OO way I've described here.

In the TTM world, Point, Rect, Hex, Layout, Orientation, Piece and Arc would be types. Tile, Grid, Board, Game and (probably) most of the others would be relvars.

Mutable (and perhaps persistent) state is best represented by relvars (of relations containing tuples) which contain values; values are defined by types.

So now we get to the nub. In OO everything is (more or less) an object. There is a useful division between those that act as pure values, and those that have modifiable state. It is common for objects to hold references to other objects, and there are lots of rules for how to do that (Patterns and Principles). So what changes in TTM, and how different is it?

TTM scalar values are directly analogous to OO value types (struct in C# or C++, by convention in Java). POSSREPs are directly analogous to multiple constructors or factory methods of OO value types. There is no such direct analog with OO mutable classes. There are no POSSREPs, no selectors (of the kind in RM Pre 4).

Your proposition is that variables of a relational type (relvars) are the only holders of mutable state, and are therefore to be considered as replacements for class objects (instances). Using my example above, how would you deal with the following?

  • We have no POSSREPs, so do we write a whole bunch of factory methods instead?
  • Relational types have a name comprising the entire heading, but my OO classes are called Grid and Board. Surely we don't want to write that full name as the type of every operator that manipulates a Grid or Board? Do we need a language extension to allow for user-defined relation type names?
  • Relvars deal naturally with collections, such as a set of Tiles, but my Grid is a singleton, which has 6 properties including a list of tiles and a list of loops on those tiles. How does this work?
  • If most relvars have to be singletons, how do we ensure this is so (and what happens if it isn't)??
  • My TileView holds a reference to a TileModel and during update asks the model: did you change? So how does the TTM equivalent do that?
  • If a scalar variable is used to hold a key, and that key is used to look up a row in a relation, isn't that just a pointer by another name?

I don't have any reason to think that TTM and its type system would fall short on tackling this problem, but I do still think that the structure of the program would be greatly different. Using TTM types and relvars and doing set-oriented updates is a lot different from the OO way.

Having written this, I started wondering whether part of the problem is the peculiar syntax of Tutorial D, which makes relational types so different from scalars in how they appear and are used in the language. Maybe if relation and tuple types had ordinary user-defined names and functions (including selectors) had named arguments instead of positional, a simpler and more uniform syntax would help. But that's a topic for another time.

[...continued from another thread.]

My proposition is that relvars are the main holders of mutable state. Mutable state can also be represented by local scalar variables within operators and maybe global but non-persistent scalar variables.

We have no POSSREPs, so do we write a whole bunch of factory methods instead?

Do you mean, "if we do not have multiple POSSREPs, do we write a whole bunch of factory methods instead?"

In object oriented languages, that's what you do.

In a D without multiple possreps, you simply write operators to convert from X to Y, where Y is some type. In Rel, with multiple possreps, you can say this:

TYPE Point 
  POSSREP cartesian { X RAT , Y RAT } 
  POSSREP polar { R RAT , THETA RAT } 
INIT 
  cartesian ( R := SQRT ( X * X + Y * Y ) , THETA := ATAN2 ( Y , X ) ) 
  polar ( X := R * COS ( THETA ) , Y := R * SIN ( THETA ) );

It says you can select a Point via cartesian(5.0, 6.0) or polar(7.81025, 0.69474). Without multiple possreps, you achieve effectively the same with:

TYPE Point 
  POSSREP cartesian { X RAT , Y RAT } ;

OPERATOR polar(R RAT, THETA RAT) RETURNS Point;
  RETURN cartesian(R * COS(THETA), R * SIN(THETA));
END OPERATOR;

From the type user's point of view, Tutorial D without multiple possreps generally looks like Tutorial D with multiple possreps.

Relational types have a name comprising the entire heading, but my OO classes are called Grid and Board. Surely we don't want to write that full name as the type of every operator that manipulates a Grid or Board? Do we need a language extension to allow for user-defined relation type names?

I'd use SAME_TYPE_AS(Grid) and SAME_TYPE_AS(Board) -- and/or SAME_HEADING_AS(...) -- where I need to refer to the type (or heading) of Grid or Board.

Relvars deal naturally with collections, such as a set of Tiles, but my Grid is a singleton, which has 6 properties including a list of tiles and a list of loops on those tiles. How does this work?

If Grid is a collection of lists in an object-oriented program, I'd probably have one relvar for the list of tiles, another relvar for the list of loops on those tiles, and so on.

If most relvars have to be singletons, how do we ensure this is so (and what happens if it isn't)??

Create the relvar, insert one tuple, then set a constraint that COUNT(TheRelvar) = 1.

My TileView holds a reference to a TileModel and during update asks the model: did you change? So how does the TTM equivalent do that?

Same way you do it in object-oriented programs -- set a TileModelChanged global flag (housed in a relvar, probably) when you update the model. The view can query the flag.

If a scalar variable is used to hold a key, and that key is used to look up a row in a relation, isn't that just a pointer by another name?

It's a reference. It's definitely not machine-dependent and volatile, like true pointers.

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 June 19, 2019, 9:55 am

 

My proposition is that relvars are the main holders of mutable state. Mutable state can also be represented by local scalar variables within operators and maybe global but non-persistent scalar variables.

And, not to be stroppy about it, when you have functions with mutable local variables[1], you have everything you need for objects.  Impure FP and OO are duals.  There's a poem about it, "Lords of the Lambda", written by the redoubtable Anon.:

Data and procedures and the values they amass,
Higher-order functions to combine and mix and match,
Objects with their local state, the messages they pass,
A property, a package, the control point for a catch—
In the Lambda Order they are all first-class.
One Thing to name them all, One Thing to define them,
One Thing to place them in environments and bind them,
In the Lambda Order they are all first-class.

I'd use SAME_TYPE_AS(Grid) and SAME_TYPE_AS(Board) -- and/or SAME_HEADING_AS(...) -- where I need to refer to the type (or heading) of Grid or Board.

Or, as you noted on the original thread, add type aliases to the language.

[1] Or their equivalent in terms of mutable boxes.  ML does not have mutable local variables but does have boxes, and many Lisp compilers transform all local mutable variables into immutable variables whose values are boxes.

Quote from johnwcowan on June 19, 2019, 12:12 pm
Quote from Dave Voorhis on June 19, 2019, 9:55 am

My proposition is that relvars are the main holders of mutable state. Mutable state can also be represented by local scalar variables within operators and maybe global but non-persistent scalar variables.

And, not to be stroppy about it, when you have functions with mutable local variables[1], you have everything you need for objects.  Impure FP and OO are duals.  There's a poem about it, "Lords of the Lambda", written by the redoubtable Anon.:

Data and procedures and the values they amass,
Higher-order functions to combine and mix and match,
Objects with their local state, the messages they pass,
A property, a package, the control point for a catch—
In the Lambda Order they are all first-class.
One Thing to name them all, One Thing to define them,
One Thing to place them in environments and bind them,
In the Lambda Order they are all first-class.

Nice. :-)

Quote from johnwcowan on June 19, 2019, 12:12 pm
Quote from Dave Voorhis on June 19, 2019, 9:55 am

I'd use SAME_TYPE_AS(Grid) and SAME_TYPE_AS(Board) -- and/or SAME_HEADING_AS(...) -- where I need to refer to the type (or heading) of Grid or Board.

Or, as you noted on the original thread, add type aliases to the language.

I don't think it was me that noted it, but no matter. I was thinking in terms of Tutorial D, which doesn't have type aliases -- which makes sense for pedagogic purposes -- but would certainly be a reasonable feature in any other D.

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 johnwcowan on June 19, 2019, 12:12 pm
Quote from Dave Voorhis on June 19, 2019, 9:55 am

I'd use SAME_TYPE_AS(Grid) and SAME_TYPE_AS(Board) -- and/or SAME_HEADING_AS(...) -- where I need to refer to the type (or heading) of Grid or Board.

Or, as you noted on the original thread, add type aliases to the language.

I don't think it was me that noted it,

That was me https://forum.thethirdmanifesto.com/forum/topic/what-is-the-purpose-of-relations-containing-tuples-relations/?part=7#postid-984859

I said 'type synonyms', but 'aliases' is better.

but no matter. I was thinking in terms of Tutorial D, which doesn't have type aliases -- which makes sense for pedagogic purposes -- but would certainly be a reasonable feature in any other D.

Does not having aliases help pedagogy? Are you thinking that writing out some long-winded type (or the almost as long-winded SAME_TYPE_AS) multiple times is like learning copper-plate handwriting? Practice makes perfect.

Quote from johnwcowan on June 19, 2019, 12:12 pm
Quote from Dave Voorhis on June 19, 2019, 9:55 am

 

My proposition is that relvars are the main holders of mutable state. Mutable state can also be represented by local scalar variables within operators and maybe global but non-persistent scalar variables.

And, not to be stroppy about it, when you have functions with mutable local variables[1], you have everything you need for objects.  Impure FP and OO are duals.  There's a poem about it, "Lords of the Lambda", written by the redoubtable Anon.:

To be clear, my intention was never to propose that OO could do what the TTM type system could not, just to argue the consequences of having no equivalent for "packets of state". Tiny immutable classes are homologous with FP values and their types, but what replaces a mutable instance variable? I accept all the suggestions of how to answer my questions, but mostly they reinforce the view that you can't get there from here, you have to start from somewhere else.

So now, in what sense are these duals?I couldn't find anything particularly helpful on the Web.

The ordinary local variable has restrictions on both its scope and its lifetime. How do you make local variables live longer than the function that created them? Are you talking closures here? Is that the missing link?

Andl - A New Database Language - andl.org

Closures, yes.  Let me sketch it:

class foo {
  foo(int i, int j) { ...}   // constructor
  int a
  int b
  int c
  int alpha(int i) {...}
  int beta(int i, int j) { ... }
}

That can be mechanically transformed into the following:

foo(i, j) {
  int guts(string type, string method, int i, int j) {
    int a = 0
int b = 0
int c = 0
if (type == "get") {

      if (method == "a") return a
      elif (method == "b") return b
      elif (method == "c") return c
    } elif (type == "set") {
      if (method == "a") { a = i; return 0}
      if (method == "b") {b = i; return 0}
      if (method == "c") {c = i; return 0}
    } elif (type == "method") {
      if (method == "alpha") { code for alpha, may use a, b, c, i}
      if (method == "beta") { code for beta, may use a, b, c, i, j}
             }
  }
return guts
}

Calling foo returns the function guts closed over i, j, a, b, c, which provides a protocol for getting, setting, and invoking methods on itself.

Same way you do it in object-oriented programs -- set a TileModelChanged global flag (housed in a relvar, probably) when you update the model. The view can query the flag.

The other responses seem perfectly reasonable, so this is the one I think to concentrate on.

  • There is a collection of tile models and a collection of tile views. In the OO case they are linked by a reference. Would you suggest that the view holds a key to find its model? Or the grid operates on both collections?
  • Do you propose that having only local and global variables is enough? The power of OO comes largely from encapsulated instance variables, long lifetime, restricted visibility. What replaces that?

 

Andl - A New Database Language - andl.org
Quote from johnwcowan on June 20, 2019, 2:03 am

Closures, yes.  Let me sketch it:

Calling foo returns the function guts closed over i, j, a, b, c, which provides a protocol for getting, setting, and invoking methods on itself.

I've used closures somewhat in C# and a lot in JS. Would I be right in saying that Java and Tutorial D do not support closures as described here?

Is this a usual thing, using closures specifically to capture "packets of state" akin to OO instances? I've only ever used closures of a few lines of code and one or two variables to solve a particular problem. This idea is new to me.

Andl - A New Database Language - andl.org
Quote from dandl on June 20, 2019, 12:51 am
Quote from johnwcowan on June 19, 2019, 12:12 pm
Quote from Dave Voorhis on June 19, 2019, 9:55 am

My proposition is that relvars are the main holders of mutable state. Mutable state can also be represented by local scalar variables within operators and maybe global but non-persistent scalar variables.

And, not to be stroppy about it, when you have functions with mutable local variables[1], you have everything you need for objects.  Impure FP and OO are duals.  There's a poem about it, "Lords of the Lambda", written by the redoubtable Anon.:

To be clear, my intention was never to propose that OO could do what the TTM type system could not, just to argue the consequences of having no equivalent for "packets of state".

I freely admit to being largely ignorant of the innards of OOP, but I'm just not seeing any difficulties here.

The equivalent for "packets of state" is the whole database; or relvar values; or tuples-in-relvars; or attribute-values-in-tuples -- depending on how small your packet needs to be. You might temporarily load some packet from the database into a program-local var (possibly with some sort of calculation/aggregation), possibly writing it back to the database before that var goes out of scope/existence.

Tiny immutable classes are homologous with FP values and their types, but what replaces a mutable instance variable? I accept all the suggestions of how to answer my questions, but mostly they reinforce the view that you can't get there from here, you have to start from somewhere else.

I don't follow what you don't follow.

So now, in what sense are these duals?I couldn't find anything particularly helpful on the Web.

The ordinary local variable has restrictions on both its scope and its lifetime. How do you make local variables live longer than the function that created them? Are you talking closures here? Is that the missing link?

In a monolithic program, you'd return the value of local variables as a result from the function -- typically inside a data structure with other local stuff. I think we're long past the days of return values being limited to a machine word on the stack/register. In a database-centric application, you'd write that value out to the database as soon as you can (subject to combining it with sufficient other data to make it a coherent/valid transaction).

  • Do you propose that having only local and global variables is enough? The power of OO comes largely from encapsulated instance variables, long lifetime, restricted visibility. What replaces that?

I'd actually propose that the only global variable you need is the database; and that local variables be considered views into it or holding slots for to-be-transactions.

Why does the power of OO come from encapsulation? (It seems to me a great deal of the trouble in OO comes from encapsulation, i.e. private mutable state; and business rules built into private method instances). Put your state into the database. Put your business rules into database constraints. If you're worried about other processes/users seeing implementation details, or poking into what they don't understand, there's ways to hide that. (Make the data content of a distinct user-defined type; don't expose the means to construct or deconstruct its innards.)

I'm not quite sure what John's getting at with the claim that FP is the dual of OO, but FP usually doesn't bundle methods inside data structures [**]. Instead it uses the type system extensively to segregate data; then an overloading mechanism to marry up the data (its type) with the correct method implementation. There's no local state inside the method/it's referentially transparent. The big win there is with multiple inheritance. (I think too much off-topic to explain the mechanics.)

[Note **] In the early noughties there was a style in FP of bundling methods amongst data 'because everybody needs objects'. There's a language O'CAML, and something called OOHaskell. They've fallen out of fashion because they're unnecessary; and because type theory and practice has advanced so much you can encode hooks to those methods as aspects of the datatype -- see 'phantom types' and 'Generalised Algebraic Data Types'. Ref the parallel thread with David L, that's all statically type-safe.

Quote from dandl on June 20, 2019, 6:04 am
Quote from johnwcowan on June 20, 2019, 2:03 am

Closures, yes.  Let me sketch it:

Calling foo returns the function guts closed over i, j, a, b, c, which provides a protocol for getting, setting, and invoking methods on itself.

I've used closures somewhat in C# and a lot in JS. Would I be right in saying that Java and Tutorial D do not support closures as described here?

Is this a usual thing, using closures specifically to capture "packets of state" akin to OO instances? I've only ever used closures of a few lines of code and one or two variables to solve a particular problem. This idea is new to me.

Java has supported closures since Java 1.1, but with some limitations (non-final variables in the outer scope aren't accessible) via an explicit mechanism called an anonymous inner class, which is essentially a cross between a conventional class and one or more lambda functions (-ish) with added verbosity.

Java has supported "true" lambdas with closures since Java 8, with fewer restrictions than earlier Java versions.

Tutorial D implies closures but doesn't specify them explicitly, but the grammar permits nested operators so in Rel inner operators close over their outer scope. Rel also defines higher order operators (see https://reldb.org/c/index.php/read/anonymous-and-first-class-operators/) so closures can be passed around.

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