www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Question on Immutability

reply Merlin Diavova <md mdiavov.com> writes:
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.

Look forward to your help.

Thanks
Merlin
Aug 30 2021
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Aug 30 2021
prev sibling parent reply Mike Parker <aldacron gmail.com> writes:
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: ```d 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: ```d import std; immutable interface Foo { void bar(); } class Baz : Foo { void bar() { writeln("Boo!"); } } void main() { immutable(Baz) b = new Baz; b.bar(); } ``` ```d 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.
Aug 30 2021
parent reply ag0aep6g <anonymous example.com> writes:
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.
Aug 30 2021
parent reply jfondren <julian.fondren gmail.com> writes:
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: ```d 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.
Aug 30 2021
parent reply Mike Parker <aldacron gmail.com> writes:
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.
Aug 31 2021
parent Merlin Diavova <md mdiavov.com> writes:
Goal achieved! Thanks all, really appreciate the assistance.
Sep 01 2021