Thread overview
Question on Immutability
Aug 30, 2021
Merlin Diavova
Aug 31, 2021
H. S. Teoh
Aug 31, 2021
Mike Parker
Aug 31, 2021
ag0aep6g
Aug 31, 2021
jfondren
Aug 31, 2021
Mike Parker
Sep 01, 2021
Merlin Diavova
August 30, 2021

Hi All,

I'm trying to understand immutability in D and it seems a bit odd.
I'm coming from dynamic languages so please forgive my ignorance and dynamic language-isms.

I want to have a base Project interface and then extend other more specific interfaces from that such as DockerEnabledProject interface, NetworkEnabledProject interface etc.

The interface implementations are immutable. I have defined some methods that allows one to change specific properties and return a new instance of the implementation.

immutable interface Project
{
    string name();
    immutable(Project) withName(string name); // Returns a new instance
}

immutable class ShellScriptCLI : Project
{
    private string _name, _slug;
    private DirectoryPath _directory;

    string name()
    {
        return this._name;
    }

    immutable(Project) withName(string name)
    {
        return new immutable ShellScriptCLI(name, this._slug, this._directory);
    }
}

...

auto project = new immutable ShellScriptCLI("Project One", "project-one", projectPath);
auto modifiedProject = project.withName("G2 Project");
assert(modifiedProject.name == "G2 Project");

After playing around the above works, Great! However I have some questions

First, why do the interfaces have to be defined as immutable interface?
The interfaces cannot be changed at runtime or instantiated.

Secondly, why does defining the return type for withName as Project give the Error: 'immutable' method 'winry.project.Project.name' is not callable using a mutable object. However changing it to immutable(Project) works as expected.

Look forward to your help.

Thanks
Merlin

August 30, 2021
On Mon, Aug 30, 2021 at 11:27:07PM +0000, Merlin Diavova via Digitalmars-d-learn wrote:
> Hi All,
> 
> I'm trying to understand immutability in D and it seems a bit odd. I'm coming from dynamic languages so please forgive my ignorance and dynamic language-isms.
> 
> I want to have a base `Project interface` and then extend other more specific interfaces from that such as `DockerEnabledProject interface`, `NetworkEnabledProject interface` etc.
> 
> The interface implementations are immutable. I have defined some methods that allows one to change specific properties and return a new instance of the implementation.

> 
> ```d
> immutable interface Project
> {
>     string name();
>     immutable(Project) withName(string name); // Returns a new instance
> }
> 
> immutable class ShellScriptCLI : Project
> {
>     private string _name, _slug;
>     private DirectoryPath _directory;
> 
>     string name()
>     {
>         return this._name;
>     }
> 
>     immutable(Project) withName(string name)
>     {
>         return new immutable ShellScriptCLI(name, this._slug,
> this._directory);
>     }
> }
> 
> ...
> 
> auto project = new immutable ShellScriptCLI("Project One", "project-one",
> projectPath);
> auto modifiedProject = project.withName("G2 Project");
> assert(modifiedProject.name == "G2 Project");
> ```
> After playing around the above works, Great! However I have some questions
> 
> First, why do the interfaces have to be defined as `immutable interface`? The interfaces cannot be changed at runtime or instantiated.
>
> Secondly, why does defining the return type for withName as `Project` give the `Error: 'immutable' method 'winry.project.Project.name' is not callable using a mutable object`. However changing it to `immutable(Project)` works as expected.

You need to declare Project.name and Project.withName with either `const` or `immutable`, like this:

     string name() const;
     immutable(Project) withName(string name) immutable;

The second `immutable` in .withName applies to the implicit `this` parameter received by every method.  Without this qualifier you cannot invoke it with an immutable object.

The interface itself does not need to be immutable; putting `immutable` on it merely makes `immutable` the default attributes in member declarations, which is likely not what you want if you will be defining mutable data fields later on.  It happens to fix your compile error because you forgot to put `const` or `immutable` on .name and .withName.

//

The best way to understand const/mutable/immutable in D is this little diagram (forgive the ASCII art):

	       const
	      /     \
	mutable     immutable

Think of it as analogous to a class hierarchy diagram (a "type hierarchy" if you will): both mutable and immutable implicit convert to const, but const does not convert to either.

Basically, `const` means the holder of the reference is not allowed to modify it.  So it doesn't matter whether the original data was mutable or immutable: as far as the recipient is concerned, it cannot modify the data, so everything is good.

`immutable` means that ALL references to the data cannot modify it (this applies across threads too).  Const data *could* be modified by somebody who happens to hold a mutable reference to it; immutable data cannot be modified, period.  This means immutable can be freely shared across threads (there are no mutable references to it, so the data never changes and any thread can read it without needing to synchronize).

Furthermore, const/immutable in D is transitive, i.e., if a reference is const, then any data it references is also implicitly const, and the data referenced by said data, etc., is also const. Ditto with immutable. We call it "turtles all the way down". :-)  In D you cannot have an immutable reference to immutable data, and if you have a const reference to something, you cannot modify anything it may refer to recursively.


T

-- 
The most powerful one-line C program: #include "/dev/tty" -- IOCCC
August 31, 2021

On Monday, 30 August 2021 at 23:27:07 UTC, Merlin Diavova wrote:

>
After playing around the above works, Great! However I have some questions

First, why do the interfaces have to be defined as `immutable interface`?
The interfaces cannot be changed at runtime or instantiated.

It isn't required. And that's related to the answer to the next question.

>

Secondly, why does defining the return type for withName as Project give the Error: 'immutable' method 'winry.project.Project.name' is not callable using a mutable object. However changing it to immutable(Project) works as expected.

Applying immutable to a class or interface does not make instances immutable. It makes member functions callable on immutable instances. Example:

import std.stdio;

interface Foo {
    void bar() immutable;
}

class Baz : Foo {
    void bar() immutable { writeln("Boo!"); }
}

void main()
{
    immutable(Baz) b = new Baz;
    b.bar();

}

Notice my application of immutable on the declarations of the member functions. This means that the bar function is callable through immutable instances of Foo.

When you apply immutable to the interface declaration, it has the effect of applying it to every member function. You can see that from the compilation error output when we modify the above to this:

import std;

immutable interface Foo {
    void bar();
}

class Baz : Foo {
    void bar() { writeln("Boo!"); }
}

void main()
{
    immutable(Baz) b = new Baz;
    b.bar();

}
onlineapp.d(7): Error: class `onlineapp.Baz` interface function `void bar() immutable` is not implemented

Member functions marked as immutable can be called on both mutable and immutable instances. Member functions without it can only be called on mutable instances.

This applies to const as well.

August 31, 2021
On 31.08.21 02:50, Mike Parker wrote:
> Member functions marked as immutable can be called on both mutable and immutable instances.

That's not true.
August 31, 2021

On Tuesday, 31 August 2021 at 05:42:22 UTC, ag0aep6g wrote:

>

On 31.08.21 02:50, Mike Parker wrote:

>

Member functions marked as immutable can be called on both mutable and immutable instances.

That's not true.

Demonstrated:

struct S {
    int x;
    int get() immutable { return x; }
}

unittest {
    auto s1 = S(1);
    const s2 = S(2);
    immutable s3 = S(3);
    assert(s1.get == 1); // Error: is not callable using a mutable object
    assert(s2.get == 2); // Error: is not callable using a `const` object
    assert(s3.get == 3);
}

s/immutable/const/ and all those uses are acceptable.

August 31, 2021

On Tuesday, 31 August 2021 at 06:15:07 UTC, jfondren wrote:

>

On Tuesday, 31 August 2021 at 05:42:22 UTC, ag0aep6g wrote:

>

On 31.08.21 02:50, Mike Parker wrote:

>

Member functions marked as immutable can be called on both mutable and immutable instances.

That's not true.

Demonstrated:

Well I'm really on my A game lately.

September 01, 2021

Goal achieved! Thanks all, really appreciate the assistance.