Parameter passing in i6/i7

When you pass parameters to a routine in i6 or i7 you pass their value, not their reference (at least that’s what i’ve understood).

When writing i6 code from within i7, you can however specify that you want to process the reference instead of the value by using
(- …{-pointer-to:parameter} … -).

But is it possible to accomplish something similar with i7 ?

For example, see the following code that implements (probably in a bad way) a feature that allows me to add some indexed text to another indexed text:

Part - Concatenating indexed texts

Chapter - Concatenation

Concatenation_A is some indexed text that varies.
Concatenation_B is some indexed text that varies.
To decide what text is the concatenation of (A - indexed text) and/with (B - indexed text) (this is concatenation):
	change concatenation_a to A;
	change concatenation_b to B;
	decide on "[concatenation_a][concatenation_b]";

Section - Synonyms

To decide what text is (A - indexed text) + (B - indexed text):
	decide on the concatenation of A and B;

Section - I6 Bindings

Concatenation_phrase is a phrase (indexed text, indexed text) -> indexed text that varies.
When play begins:change concatenation_phrase to concatenation;

Include(- Global concatenation_phrase; -) after "Definitions.i6t".
The concatenation_phrase variable translates into i6 as "concatenation_phrase".




Chapter - Adding indexed text to indexed text

To add (T - indexed text) to (target - indexed text):	
	(- BlkValueCast({-pointer-to:target}, INDEXED_TEXT_TY, TEXT_TY, (concatenation_phrase-->1)({target}, {T}));  -).

Is it possible to rewrite the last function in i7 ?

No.

Last time I answered an I7 question “no”, I was wrong… I think it’s no, though.

However, you shouldn’t need call-by-reference to do what you want – assuming I’m reading your example correctly. Indexed text objects are mutable. You can use one of the “replace … in TEXT with …” phrases to change the contents of an indexed text that you were passed (by value).

Another way to simplify the same code is with the bit ((+ concatenation +)-->1)({target}, {T}) , freeing you from using [code]Section - I6 Bindings

Concatenation_phrase is a phrase (indexed text, indexed text) -> indexed text that varies.
When play begins:change concatenation_phrase to concatenation;

Include(- Global concatenation_phrase; -) after “Definitions.i6t”.
The concatenation_phrase variable translates into i6 as “concatenation_phrase”.

[/code]

Sadly, no.

There are probably enough standard phrases (defined using I6) to do all the indexed text manipulations you’d want, but this limitation really hurts when you’re working with lists or dynamic relations.

When play begins:
	let t be some indexed text;
	let t be "hello";
	test_add "X" to t;
	say t;
	say "[line break]";
	replace the text t in t with "[t]X"; 
	say t;

To test_add (T - indexed text) to (target - indexed text):
	replace the text target in target with "[target][T]"; 

This produces:

Hence the impossibility to implement the aforementioned “add” text routine. For the same reason,

To decide what indexed text is pointer to (T - indexed text):
	(- {-pointer-to:T} -).

won’t work. I guess it’s because at the i6 level {-pointer-to:x} variables are translated as values. Everything in i6 is passed by value except objects (at least that’s what the following piece of code seems to imply)

A test object is a kind of thing.
It has some indexed text called the test string. The test string of a test object is usually "FAIL".

The tested object is a test object;

When play begins:
	launch_test_on the tested object;
	say the test string of the tested object;

To launch_test_on (O - test object):
	change the test string of O to "SUCCESS".

produces

So if you want to directly process a variable by its reference (i.e. without using a “change x to y”), you’ll have to use i6 inline code.
IMO a feature that would allow the author to write I7 “phrases” not as i6 routines but as i7 inline expansions would be much welcomed.

For example my “add” text routine would read as follow:

Expand add (T - indexed text) to (target - indexed text):
	replace the text target in target with "[target][T]";

and thus,

When play begins:
	let t be some indexed text;
	let t be "hello";
	add "X" to t;

would be (strictly ?) equivalent to

When play begins:
	let t be some indexed text;
	let t be "hello";
	replace the text t in t with "[t]X";

If i7 had a system of that sort I think less people would need to use i6.

Also,

thanks ! I knew i could write stuffs like this, but hell, it was totally out of my mind.

1 Like

Looking at your first example – okay, I see what you mean. I thought that would work, but indexed variables have weirder semantics than I thought.

Arguably it should have worked. The documentation on “replace” doesn’t give warnings about scope.

Returning to your general suggestion: many programming languages get along fine without call-by-reference parameters. I am not a big fan of them myself. I see the “inline” behavior of I6 inclusions as a hack of necessity, not a feature that should be expanded.

This doesn’t mean it’s a disastrous idea – but I do think it’s potentially very confusing. I7 already has a lot of value-reference confusion to go around.

Usually when Inform starts dealing with pointers and such, it always passes it to one of the Blk*() functions which… works some sort of magic. I do know that Inform 7 has a mantra of “one block [of memory], one pointer” which is how it avoids issues with garbage collection and memory leaks, and the pointer-to stuff is something of a workaround.
I’d love to have a primer on the details of how that works, if only for knowledge. I haven’t experimented with pointer-to, but I do know just from C coding that your above code wouldn’t work, because a pointer-to-indexed-text isn’t the same thing as indexed text itself.
If you’ve the time & inclination, try creating, say, a new unit called pointer that’s written with a @ in front of it. And have the {-pointer-to:T} phrase return that unit. Like this:[code]A pointer is a kind of value. @99999 specifies a pointer.

To decide what pointer is the pointer to (T - indexed text): (- {-pointer-to:T} -).

Space is some indexed text that varies.

WHen play begins, say pointer to space.[/code]Then try writing the same phrase using indexed text directly, and using the pointer directly, and see how the generated I6 differs. You might be able to figure something out.

Which, by the way, this code works if you remember to return the answer.[code]
When play begins:
let t be some indexed text;
let t be “hello”;
say test_add “X” to t;
[ say t;]
say “[line break]”;
replace the text t in t with “[t]X”;
say t;

To decide which indexed text is test_add (T - indexed text) to (target - indexed text):
let X be indexed text;
let X be “[target][T]”;
decide on X. [/code]It produces:
helloX
helloX
…as you aimed for.

That last example is not what we’re talking about. We already know that I7 handles the “pass an argument in, return a result back” pattern. The question is, why doesn’t the “replace” phrase for indexed text act to mutate the text in the (perhaps) obvious way?

Maybe indexed texts are supposed to not be mutable, and “replace” is behaving analogously to “increase X by 1”. But I don’t know whether that’s the plan, or whether this is a bug.

It’s kind of depressingly simple. Every location that can hold a block-type value (local variables, global variables, properties, table entries (*)) stores a pointer to a unique heap block. When you assign a new value, it’s copied from the old block to the new one, and the new one is resized if necessary - I7’s memory manager can even fragment the block if there isn’t enough space at the original location.

This greatly simplifies memory management. No two I7 variables ever point to the same heap block, so every block value can be mutated or freed independently, with no need for reference counting or garbage collection. But the price for that simplicity is inefficient use of memory (especially painful on the Z-machine) and wariness of pass-by-reference.

(* but, IIRC, not list entries, where an assignment frees the old value and replaces it with a pointer to a new copy of the value)

Actually, in I7, it is. {-pointer-to:T} just stops I7 from creating a copy of the indexed text, as it normally does with plain {T} in some cases. For example:

[code]To print pointer to (T - indexed text):
(- INDEXED_TEXT_TY_Say({-pointer-to:T}); -).

To print plain old (T - indexed text):
(- INDEXED_TEXT_TY_Say({T}); -).

When play begins:
let T be indexed text;
let T be “hello”;
print pointer to T;
print plain old T;[/code]
Generates:

! [3: print pointer to t] INDEXED_TEXT_TY_Say(t_0); ! phrase 4 ! [4: print plain old t] INDEXED_TEXT_TY_Say(BlkValueCopy((blockv_stack-->(I7BASPL+1)), t_0));
So with {-pointer-to:T} the I6 routine receives the address of the same indexed text heap object that is in “T”, whereas with {T} a copy is made first.

Whether or not a copy is made also seems to depend on the context in which the code is expanded:

[code]To decide which indexed text is pointer to (T - indexed text): (- {-pointer-to:T} -).

To decide which indexed text is plain old (T - indexed text): (- {T} -).

To print (T - indexed text): say T.

When play begins:
let T be indexed text;
let T be “hello”;
print pointer to T;
print plain old T;
say pointer to T;
say plain old T;[/code]
Even without {-pointer-to:T}, no copy is made when the resulting value is passed to “say”:

! [3: print pointer to t] (PHR_741_r3 (BlkValueCopy((blockv_stack-->(I7BASPL+1)), t_0 ))); ! phrase 4 ! [4: print plain old t] (PHR_741_r3 (BlkValueCopy((blockv_stack-->(I7BASPL+2)), BlkValueCopy((blockv_stack-->(I7BASPL+3)), t_0) ))); ! phrase 5 ! [5: say pointer to t] say__p=1;ParaContent(); print (INDEXED_TEXT_TY_Say) t_0 ; .L_Say1; .L_SayX1; ! phrase 6 ! [6: say plain old t] say__p=1;ParaContent(); print (INDEXED_TEXT_TY_Say) t_0 ; .L_Say2; .L_SayX2;

It does - it mutates “target”. But that’s not the same indexed text value as the one that was passed in; it’s a copy, and it’s deallocated as soon as the phrase exits.

They are mutable. “replace” is implemented by IT_ReplaceText in IndexedText.i6t, which modifies the value in place.

Okay, thanks for the explanation.

This seems like it deserves some category of its own – pass-by-reference, pass-by-value, pass-by-a-different-value…

Well, it’s not much different from the way structs are passed by value in C: the entire struct is copied to a new location, and the called function can freely mutate it without affecting the caller’s copy. The difference is that in I7 the structures live on the heap instead of the stack.

Oh-ho! That is interesting. Thanks for the braindump, Jesse.

“Say” being an exception doesn’t surprise me, as Say is a macro that expands to a sequence of functions, rather than being a function itself.

Thanks again.