I7: Tables and functional programming

Given a table that has a column with objects in each row, and other columns headed by property names, such as this one:

Table of Object Parameters object strength size available salamander 12 20 true Kitchen -- 800 -- north -- -- true

…can you, for each row of the table, apply the given value for each property to the object–without knowing in advance what kinds of object or specific properties will be used? In other words, can a phrase be written using Inform’s new(ish) “functional programming” capabilities that would be able to recognize a property from a table column heading and apply the given value to the required object? How do you reference to the column/property in a way that Inform understands?

(Note: I’m aware that you can initially define objects using such a table; I want to use the table to change properties during play, without hardcoding property names into the code that reassigns values using lots of if statements.)

Hope this explanation is clear enough… Thanks!

–Erik

I’m confused if you’re actually using properties at all, or are just calling the columns “properties”.

The functional programming aspects of Inform are basically syntactic sugar – they don’t do any magic that the rest of the language can’t already do. But since it seems you might be constructing something like { { strength, height }, { 10, 45 } } kind of lists that represent changes to be made to the objects in that table, you can of course make a new decide phrase which accepts a list of K based table columns and a list of K, and use said phrase on a filter/map/reduce conveyor belt. (Well, it would accept a single list of list of values, since “value” is the only commonality between a table column and a number. You get the idea, I hope.)

I could be completely missing your question, though.

No, I want to use actual properties of actual objects, and to use the table to change them in-game. The main difficulty–probably insurmountable–is how to communicate to Inform that it should treat the column name as the name of property. The I7 compiler (?) can use table columns in just this way to define objects initially, but I’m not seeing how one could do this in any situation except for the special case of declaring objects.

Something like (pseudo-code):

repeat through the Table of Object Parameters: let item be the object entry; repeat through columns: if the item has the property (column name): now the (column name) of the item is (column name) entry.

–Erik

Unfortunately, there’s no good way to iterate through table columns in pure I7 because table columns don’t share a common kind; number valued table columns, for instance, are difficult to reconcile with indexed text valued table columns. Also, by the time ni has emitted the I6 any correspondence between table column names and property names has been lost.

You can do what you want by dropping to I6 and associating columns with properties yourself. I’m afraid it’s a little messy:

[spoiler][code]Section “Setup”

To decide what number is (V - a value) converted to a number: (- {V} -).
To decide what number valued table column is the column with column number (N - a number): (- {N} -).

To decide whether (O - an object) has property number (P - a number): (- WhetherProvides({O},({P}<attributed_property_offsets_SIZE)&&((attributed_property_offsets–>{P})~=-1),{P}) -).
To store non-block value (V - a value) as property number (P - a number) in (O - an object): (- {O}.{P}={V}; -).
To store block value (V - a value) as property number (P - a number) in (O - an object): (- BlkValueCopy(GProperty(OBJECT_TY,{O},{P}),{V}); -).

To decide what number is the number of columns in (T - a table name): (- ({T}–>0) -).
To decide what number is table column (I - a number) in (T - a table name): (- ((({T}–>{I})–>1)&TB_COLUMN_NUMBER) -).
To decide whether table column (C - a number) is a block-value table column: (- KOVIsBlockValue(TC_KOV({C})) -)

To decide whether there is a value at row (R - a number) and column (C - a number) in (T - a table name): (- ExistsTableLookUpEntry({T},{C},{R}) -).
To decide what number is the value at row (R - a number) and column (C - a number) in (T - a table name): (- TableLookUpEntry({T},{C},{R}) -).

Column correspondence relates numbers to numbers. The verb to be the column corresponding to implies the column correspondence relation.
To associate (C - a value of kind K valued table column) with (P - a K valued property):
let the column number be C converted to a number;
let the property number be P converted to a number;
now the column number is the column corresponding to the property number.

To apply the properties in (T - a table name):
repeat with the column index running from two [not one; that’s the object column] to the number of columns in T:
let the column number be the table column column index in T;
unless the column number relates to a number by the column correspondence relation:
next;
let the property number be the number that the column number relates to by the column correspondence relation;
repeat with the row index running from one to the number of rows in T:
let the named object be the object column in row row index of T;
unless the named object has property number property number:
next;
unless there is a value at row row index and column column number in T:
next;
let the value be the value at row row index and column column number in T;
if table column column number is a block-value table column:
store block value value as property number property number in the named object;
otherwise:
store non-block value value as property number property number in the named object.

Section “Demonstration”

The Kitchen is a room.
Here is an animal called the salamander.

Every object has a number called strength.
Every object has a number called size.
Every direction has a truth state called available.
Every animal has some indexed text called tagline.

Table of Object Parameters
object column strength column size column available column tagline column (indexed text)
salamander 12 201234 true “x”
Kitchen – 800 – “y”
north 92 – true “z”

When play begins:
associate the strength column with strength;
associate the size column with size;
associate the available column with available;
associate the tagline column with tagline.

Instead of jumping:
apply the properties in the Table of Object Parameters;
say “Done.”

Test me with “showme salamander / showme north / jump / showme salamander / showme north”.[/code][/spoiler]

Ah. Then my first question would be “Good gods, why?” but that would be rude. :slight_smile:

Um… maybe copy-paste the relevant phrases dealing with tables from the standard rules into your WIP, but change the parameter types from K valued table column to K valued property (or whatever it’s called). And so on.

Is my best guess…

Well, I’m casting about for ways to deal with some annoying weaknesses in Inform. I want to provide the simplest possible structure, in an extension, for animating any number of independent graphics elements using the glulx timer. Graphic elements are implemented as objects, and different kinds of elements can have different properties; it’s the properties that define how they are drawn. There are probably fifteen or more possible properties to act on (colors, scaling parameters, etc.), and properties of the window can also be animated (origin coordinate, background color).

That’s a lot of potential complexity, but for simple animations, procedural rules (“add 10% to the scaling ratio of the object each frame until it reaches 100%”) suffice, while switch statements can work for animations that can’t be described procedurally, e.g.:

if the current frame is: -- 1: now the origin coordinate of the bunny is {12, 12}; -- 2: now the origin coordinate of the bunny is {200, 95}; -- 3: now the origin coordinate of the bunny is {66, 230};

But try to do any more in that same animation, and you hit a wall: Inform won’t let you provide multiple instructions on the same line of a switch statement. This doesn’t work, for example:

if the current frame is: -- 1: now the origin coordinate of the bunny is {12, 12}; now the origin coordinate of the carrot is {200, 95}; -- 2: now the origin coordinate of the bunny is {200, 95}; now the origin coordinate of the carrot is {66, 230}; -- 3: now the origin coordinate of the bunny is {66, 230};

Even if it did work, that method would start to get cumbersome: Multiple objects and multiple properties of each of those objects might be changing in the same frame, and I don’t really want the author to have to type “now…” phrases for each object-property combination needed.

A generalized record or hash might be appropriate for this kind of thing, but aside from the “combination” type, which is not yet available at the I7 level–and may never be–there is no such thing in Inform. Lists aren’t a solution, since you can’t have multiple types in the same list.

And that leaves tables (though I am open to other solutions!–let me know!). As long as an author can use only the columns he needs, tables can be relatively compact. For example, the following table performs three different operations on two graphic entities, sliding a cover image in from the left and painted text (the title) in from the right, while at the same time swapping out the letters in the title:

Table of Cover Animation frame object origin text-string 1 Cover-image {-200, 20} -- 1 Title-slug {800, 30} "A@7D^arkz" 2 Cover-image {-100, 20} -- 2 Title-slug {700, 30} "A@rd^arkz" 3 Cover-image {0, 20} -- 3 Title-slug {600, 30} "A@rdvarkz"

If anyone has made it this far and doesn’t find convincing my justification for focusing on tables – please feel free to post! I’m happy to consider other ideas. (Note that the drawing by properties scheme is set in stone, though…)

In the actual extension, the kind of compact, semi-automated animation I’m discussing will be just one of several options. There will also be some prepackaged procedural animations of varying complexity, and the author will also be able to supply her own rules.

Wow, this is great–thanks Emacs! I do wish that this was a feature that Inform/NI provided, because even with this code, authors will need to do some mild bookkeeping if they want to add new properties. That’s not such a big deal, but I hate having to require those kinds of tasks. I’ll need to figure out how to add some optional columns that aren’t properties (for callback functions, among other things), but this ought to work. Thanks again!

–Erik

Oh you wanted to block copy from tables to properties. I misunderstood twice. I point at EmacsUser and say “what he said”.

Er, my previous post doesn’t handle attributes correctly :blush:. Let me try again:

[spoiler][code]Section “Setup”

To decide what number is (V - a value) converted to a number: (- {V} -).

To decide whether property number (P - a number) is an attributed property: (- ({P}<attributed_property_offsets_SIZE)&&((attributed_property_offsets–>{P})~=-1) -).
To decide whether (O - an object) has property number (P - a number): (- WhetherProvides({O},({P}<attributed_property_offsets_SIZE)&&((attributed_property_offsets–>{P})~=-1),{P}) -).

To store non-block value (V - a value) as attributed property number (P - a number) in (O - an object): (- SetEitherOrProperty({O},{P},{V}==0); -).
To store non-block value (V - a value) as property number (P - a number) in (O - an object): (- {O}.{P}={V}; -).
To store block value (V - a value) as property number (P - a number) in (O - an object): (- BlkValueCopy(GProperty(OBJECT_TY,{O},{P}),{V}); -).

To decide what number is the number of columns in (T - a table name): (- ({T}–>0) -).
To decide what number is table column (I - a number) in (T - a table name): (- ((({T}–>{I})–>1)&TB_COLUMN_NUMBER) -).
To decide whether table column (C - a number) is a block-value table column: (- KOVIsBlockValue(TC_KOV({C})) -)

To decide whether there is a value at row (R - a number) and column (C - a number) in (T - a table name): (- ExistsTableLookUpEntry({T},{C},{R}) -).
To decide what number is the value at row (R - a number) and column (C - a number) in (T - a table name): (- TableLookUpEntry({T},{C},{R}) -).

Column correspondence relates numbers to numbers. The verb to be the column corresponding to implies the column correspondence relation.
To associate (C - a value of kind K valued table column) with (P - a K valued property):
let the column number be C converted to a number;
let the property number be P converted to a number;
now the column number is the column corresponding to the property number.

To apply the properties in (T - a table name):
repeat with the column index running from two [not one; that’s the object column] to the number of columns in T:
let the column number be the table column column index in T;
unless the column number relates to a number by the column correspondence relation:
next;
let the property number be the number that the column number relates to by the column correspondence relation;
repeat with the row index running from one to the number of rows in T:
let the named object be the object column in row row index of T;
unless the named object has property number property number:
next;
unless there is a value at row row index and column column number in T:
next;
let the value be the value at row row index and column column number in T;
if table column column number is a block-value table column:
store block value value as property number property number in the named object;
otherwise if property number property number is an attributed property:
store non-block value value as attributed property number property number in the named object;
otherwise:
store non-block value value as property number property number in the named object.

Section “Demonstration”

The Kitchen is a room.
Here is an animal called the salamander.

Every object has a number called strength.
Every object has a number called size.
A direction can be available.
Every animal has some indexed text called tagline.

Table of Object Parameters
object column strength column size column available column tagline column (indexed text)
salamander 12 201234 true “x”
Kitchen – 800 – “y”
north 92 – true “z”

When play begins:
associate the strength column with strength;
associate the size column with size;
associate the available column with available;
associate the tagline column with tagline.

Instead of jumping:
apply the properties in the Table of Object Parameters;
say “Done.”

Test me with “showme salamander / showme north / jump / showme salamander / showme north”.[/code][/spoiler]

Also, I know it doesn’t address all of your concerns, but you can writeRule: if the current frame is: -- 1: now the origin coordinate of the bunny is {12, 12}; now the origin coordinate of the carrot is {200, 95}; -- 2: now the origin coordinate of the bunny is {200, 95}; now the origin coordinate of the carrot is {66, 230}; -- 3: now the origin coordinate of the bunny is {66, 230}.as long as the – : lines have no other text on them.

Even better, thanks!

I would not have thought to try that–I have always thought of this syntax as a special case, and didn’t consider that tabs and line breaks would allow for grouping in the same way as with full “if” statements. Thanks!

–Erik

Well, that’s not right either, because I forgot that properties’ and attributes’ numbers can coincide. For instance,

There is a room.
To decide what number is description converted to a number: (- description -).
To decide what number is locked converted to a number: (- locked -).

When play begins:
	showme description converted to a number;
	showme locked converted to a number.

So best to stick to non-attributes, I guess:

Section "Setup"

To decide what number is (V - a value) converted to a number: (- {V} -).

To decide whether (O - an object) has property number (P - a number): (- WhetherProvides({O},0,{P}) -).

To store non-block value (V - a value) as property number (P - a number) in (O - an object): (- {O}.{P}={V}; -).
To store block value (V - a value) as property number (P - a number) in (O - an object): (- BlkValueCopy(GProperty(OBJECT_TY,{O},{P}),{V}); -).

To decide what number is the number of columns in (T - a table name): (- ({T}-->0) -).
To decide what number is table column (I - a number) in (T - a table name): (- ((({T}-->{I})-->1)&TB_COLUMN_NUMBER) -).
To decide whether table column (C - a number) is a block-value table column: (- KOVIsBlockValue(TC_KOV({C})) -)

To decide whether there is a value at row (R - a number) and column (C - a number) in (T - a table name): (- ExistsTableLookUpEntry({T},{C},{R}) -).
To decide what number is the value at row (R - a number) and column (C - a number) in (T - a table name): (- TableLookUpEntry({T},{C},{R}) -).

Column correspondence relates numbers to numbers.  The verb to be the column corresponding to implies the column correspondence relation.
To associate (C - a value of kind K valued table column) with (P - a K valued property):
	let the column number be C converted to a number;
	let the property number be P converted to a number;
	now the column number is the column corresponding to the property number.

To apply the properties in (T - a table name):
	repeat with the column index running from two [not one; that's the object column] to the number of columns in T:
		let the column number be the table column column index in T;
		unless the column number relates to a number by the column correspondence relation:
			next;
		let the property number be the number that the column number relates to by the column correspondence relation;
		repeat with the row index running from one to the number of rows in T:
			let the named object be the object column in row row index of T;
			unless the named object has property number property number:
				next;
			unless there is a value at row row index and column column number in T:
				next;
			let the value be the value at row row index and column column number in T;
			if table column column number is a block-value table column:
				store block value value as property number property number in the named object;
			otherwise:
				store non-block value value as property number property number in the named object.

Section "Demonstration"

The Kitchen is a room.
Here is an animal called the salamander.

Every object has a number called strength.
Every object has a number called size.
Every animal has some indexed text called tagline.

Table of Object Parameters
object column	description column	strength column	size column	tagline column (indexed text)
salamander	"A mythical lizardlike creature said to live in fire or to be able to withstand its effects."	12	201234	"x"
Kitchen	--	--	800	"y"
north	--	92	--	"z"

When play begins:
	associate the description column with description;
	associate the strength column with strength;
	associate the size column with size;
	associate the tagline column with tagline.

Instead of jumping:
	apply the properties in the Table of Object Parameters;
	say "Done."

Test me with "showme salamander / showme north / jump / showme salamander / showme north".

(Based on your other posts, I take it that you’re using a different strategy. But I didn’t want to leave up code that I know to be wrong.)

Thanks for the fix/trim! I’ve got the code on file and may yet figure out a way to use it. I had intended to use the table approach alongside the other approach, but I’m finding that using a table will require a bit more bookkeeping from authors than I want to make necessary. I may still offer it as a “plug-in” extension later on if interest points that way…

(Basically, the table would work as a kind of keyframe register, with automatic tweening of frames in between the keys. This would be an easy way to specify complex animations at one time and with a minimum of fiddliness. For example, you could have a shape move in a continuous ellipse on the screen with just 2 entries in the list, and three such shapes zipping all over the place with just 6 lines. However, you also need a way to store data about the state of the tweens, which is one of the areas where the bookkeeping comes in…)

Anyway, thanks again for the help!

–Erik