Thread overview
trying to understand in, inout, and ref...
Jan 22, 2020
mark
Jan 22, 2020
Paul Backus
Jan 22, 2020
mark
Jan 22, 2020
Adam D. Ruppe
Jan 22, 2020
mark
January 22, 2020
I have these code snippets:

alias WordList = string[];
alias WordSet = int[string]; // key = word; value = 0

WordList generate(WordSet allWords, int steps) {
    WordList ladder; // will be changed in update()
    auto words = allWords.dup; // will be changed in update()
    auto compatibles = allWords.dup; // will be changed in this function
    auto prev = update(ladder, words, compatibles);
    // TODO
    return ladder;
}

string update(WordList ladder, WordSet words, WordSet compatibles) {
    auto word = ""; // TODO random string from compatibles
    // TODO remove word from words; add word to ladder
    return word;
}

Regarding generate(): allWords should never be changed (generate is called in a loop with the same allWords every time) -- so should it be `in WordSet allWords`?

Regarding update(): ladder and words are both modified in update -- so should they be `ref WordList ladder` and `ref WordSet words`? And if so, do I need to change the update() call in the generate() function? compatibles is (will be) modified in generate() but not in update(), so should it be `in WordSet compatibles`?
January 22, 2020
On Wednesday, 22 January 2020 at 10:49:07 UTC, mark wrote:
> Regarding generate(): allWords should never be changed (generate is called in a loop with the same allWords every time) -- so should it be `in WordSet allWords`?

For parameters that shouldn't be changed, use const. So it should be `const WordSet allWords`.

> Regarding update(): ladder and words are both modified in update -- so should they be `ref WordList ladder` and `ref WordSet words`? And if so, do I need to change the update() call in the generate() function?

Yes, they should be `ref`. No, you do not have to change the update() call.

> compatibles is (will be) modified in generate() but not in update(), so should it be `in WordSet compatibles`?

It should be `const WordSet compatibles`.
January 22, 2020
On Wednesday, 22 January 2020 at 14:23:53 UTC, Paul Backus wrote:
> On Wednesday, 22 January 2020 at 10:49:07 UTC, mark wrote:
>> Regarding generate(): allWords should never be changed (generate is called in a loop with the same allWords every time) -- so should it be `in WordSet allWords`?
>
> For parameters that shouldn't be changed, use const. So it should be `const WordSet allWords`.

That single change produces (using LDC 1.19.0 - D 2.089.1):

./wordladder.d(52): Error: function wordladder.update(string[] ladder, int[string] words, const(int[string]) compatibles) is not callable using argument types (string[], const(int)[string], const(int)[string])
./wordladder.d(52):        cannot pass argument words of type const(int)[string] to parameter int[string] words
Failed: ["/home/mark/opt/ldc2-1.19.0-linux-x86_64/bin/ldmd2", "-v", "-o-", "./wordladder.d", "-I."]

So I've rolled it back.

>> Regarding update(): ladder and words are both modified in update -- so should they be `ref WordList ladder` and `ref WordSet words`? And if so, do I need to change the update() call in the generate() function?
>
> Yes, they should be `ref`. No, you do not have to change the update() call.

I've done this but my impression from the docs is that passing slices and associative arrays is already done by reference so these aren't needed? (I can't tell yet because I haven't written the modifying code.)

>> compatibles is (will be) modified in generate() but not in update(), so should it be `in WordSet compatibles`?
>
> It should be `const WordSet compatibles`.

Done that and it compiles fine.

Thanks.

January 22, 2020
On Wednesday, 22 January 2020 at 15:26:06 UTC, mark wrote:
> I've done this but my impression from the docs is that passing slices and associative arrays is already done by reference so these aren't needed?

They are pointers passed by value.

If you're familiar with C, think of passing

struct Array {
   size_t length;
   element* ptr;
}


The elements are passed by ref there; they aren't copied to the function and any changes to them will be visible outside.

BUT if you change the length of it or reallocate it in any way those changes are NOT seen outside.

So with AAs and slices, if you just want to work with existing elements, no need for ref. But if you are going to do any kind of resizing - adding or removing elements - ref is likely what you want.
January 22, 2020
On Wednesday, 22 January 2020 at 15:33:44 UTC, Adam D. Ruppe wrote:
> On Wednesday, 22 January 2020 at 15:26:06 UTC, mark wrote:
>> I've done this but my impression from the docs is that passing slices and associative arrays is already done by reference so these aren't needed?
>
> They are pointers passed by value.
>
> If you're familiar with C, think of passing
>
> struct Array {
>    size_t length;
>    element* ptr;
> }
>
>
> The elements are passed by ref there; they aren't copied to the function and any changes to them will be visible outside.
>
> BUT if you change the length of it or reallocate it in any way those changes are NOT seen outside.
>
> So with AAs and slices, if you just want to work with existing elements, no need for ref. But if you are going to do any kind of resizing - adding or removing elements - ref is likely what you want.

Thanks - that's exactly what I needed to know!

(I'm also very much enjoying your D Cookbook.)
January 22, 2020
On 1/22/20 10:33 AM, Adam D. Ruppe wrote:

> 
> BUT if you change the length of it or reallocate it in any way those changes are NOT seen outside.
> 
> So with AAs and slices, if you just want to work with existing elements, no need for ref. But if you are going to do any kind of resizing - adding or removing elements - ref is likely what you want.

So just to clarify this a bit for AAs. Adding or removing elements from an AA DOES get seen outside, even if you don't pass by ref, except for one case -- the AA is in its initial state.

The reason is because an AA is actually a pointer-to-implementation (pImpl) struct, which is initialized to null, but allocated on first element added.

So if you don't add any elements, and pass it by value, you are passing null by value, and adding elements will allocate it. But the result doesn't get seen back at the parameter you passed.

However, if you add one element, and then pass it, the implementation is already allocated and does not change locations. So then you can add more elements even if you pass by value, and the implementation stays at the same location.

example:

void foo(int[int] p)
{
   p[1] = 1; p[2] = 2;
}

int[int] aa;

foo(aa);
assert(aa.length == 0); // new allocation in foo not seen
aa[0] = 0; // first initialization, no longer null
foo(aa);
assert(aa.length == 3); // now, you see the changes

aa.clear(); // remove all elements, but don't deallocate.
assert(aa.length == 0);
foo(aa);
assert(aa.length == 2); // was already preallocated, so impl stays the same.

This aspect is very confusing to many people.

-Steve