www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Named constructors

reply JN <666total wp.pl> writes:
Let's say you want to have an Angle class, which holds radians 
internally but can be initialized with degrees or radians.

```d
class Angle
{
     float radians;

     this(float rad) : radians(rad) { }
     this(float degs) : radians(degs * (PI / 180.0f)) { }
}

Angle a = new Angle(90.0f);
```

Won't work, because you can't have two constructors with same 
args. Of course you can work it around by e.g. wrapping them in 
some "Degree" and "Radian" struct so that they're separate type 
and overloads.

Next option, static factory methods:

```d
class Angle
{
     float radians;

     // hide constructor so that users use the factory methods
     private this(float rad) { radians = rad; }

     static Angle fromRadians(float rad) { return new Angle(rad); }
     static Angle fromDegrees(float degs) { return new Angle(degs 
* (PI / 180.0f)); }
}

Angle a = Angle.fromDegrees(90.0f);
```

This works, but is awkward because suddenly you don't use the new 
keyword and it's not immediately obvious that these methods are 
meant to be used for construction of the object. After typing 
"Angle." an IDE will suggest all static methods on the class 
which can be a long list.

Named constructors to the rescue:

```d
class Angle
{
     float radians;

     this.fromRadians(float rad) : radians(rad) { }
     this.fromDegrees(float degs) : radians(degs * (PI / 180.0f)) 
{}
}

Angle a1 = new Angle.fromRadians(PI / 2.0f);
Angle a2 = new Angle.fromDegrees(90.0f);
```

Much clearer for the user, user can type "new Angle." and the IDE 
would suggest only constructors instead of all of the static 
methods on the class. Assumption would be that if named 
constructors exist, there is no implicit argumentless constructor 
so Angle a3 = new Angle() won't work anymore.
Nov 08
next sibling parent Arafel <er.krali gmail.com> writes:
On 9/11/24 1:02, JN wrote:
 ```d
 class Angle
 {
      float radians;
 
      this.fromRadians(float rad) : radians(rad) { }
      this.fromDegrees(float degs) : radians(degs * (PI / 180.0f)) {}
 }
 
 Angle a1 = new Angle.fromRadians(PI / 2.0f);
 Angle a2 = new Angle.fromDegrees(90.0f);
 ```
This would interfere with, and can be simulated through, nested classes: ```d enum PI=3.141592f; class Angle { float radians; this(float rad) { radians = rad; } static class FromRadians : Angle { this(float rad) { super(rad); } } static class FromDegrees : Angle { this(float degs) { super(degs * (PI / 180.0f)); } } } void main() { Angle a1 = new Angle.FromRadians(PI / 2.0f); Angle a2 = new Angle.FromDegrees(90.0f); } ``` If you don't want to allow direct instantiations of `Angle`, you can declare it `abstract`, or disable its constructor.
Nov 09
prev sibling next sibling parent IchorDev <zxinsworld gmail.com> writes:
On Saturday, 9 November 2024 at 00:02:52 UTC, JN wrote:
 Much clearer for the user, user can type "new Angle." and the 
 IDE would suggest only constructors instead of all of the 
 static methods on the class. Assumption would be that if named 
 constructors exist, there is no implicit argumentless 
 constructor so Angle a3 = new Angle() won't work anymore.
1. Why are your examples using C++ syntax? You should provide examples that at least compile with *minimal* modification so that people can actually work off of them and test them. Garbage in, garbage out. 2. When your DIP is based on a problem with a feature of an IDE, then the problem lies with your IDE, not the language. The language doesn’t shape itself around IDEs, or else we’d have to discard templates, mixins, and aliases. 3. Is there any reason you can’t just use an external factory function? This is very common practice: ```d Angle angle(float deg) => new Angle(deg*PI/180f); Angle angle(float rad) => new Angle(rad); auto a = angle(deg: 90); auto b = angle(rad: PI/2); ```
Nov 12
prev sibling next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Saturday, 9 November 2024 at 00:02:52 UTC, JN wrote:
 ...
 Won't work, because you can't have two constructors with same 
 args. Of course you can work it around by e.g. wrapping them in 
 some "Degree" and "Radian" struct so that they're separate type 
 and overloads.
 ...
See also the example in https://dlang.org/phobos/std_sumtype.html SDB 79
Nov 24
prev sibling parent reply Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
On Saturday, 9 November 2024 at 00:02:52 UTC, JN wrote:
 Let's say you want to have an Angle class, which holds radians 
 internally but can be initialized with degrees or radians.
I would refactor this into a type-driven design via ```d struct Radians { private float _value; // various property functions to access `_value`. } struct Degrees { private float _value; // various property functions to access `_value`. } class Angle { float radians; this(Radians rad) : radians(rad) { } this(Degrees degs) : radians(degs * (PI / 180.0f)) { } } ``` . Now you can use ```d Angle(Radians(0.0)); Angle(Degrees(0.0)); ``` or ```d Angle(rad: Radians(0.0)); Angle(deg: Degrees(0.0)); ``` for extra verbosity. This is the beginning of a units of measurements module/package similar to, for instance, https://code.dlang.org/packages/units-d. See also https://en.wikipedia.org/wiki/Unit_of_measurement.
Dec 11
parent Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
On Thursday, 12 December 2024 at 07:55:14 UTC, Per Nordlöw wrote:
 On Saturday, 9 November 2024 at 00:02:52 UTC, JN wrote:
 Let's say you want to have an Angle class, which holds radians 
 internally but can be initialized with degrees or radians.
1. Note however that when you already have written `Radians` and `Degrees` and their mutual conversion there's no need to write `Angle`. 2. Some would argue that it's better to use the singular forms `Radian` and `Degree` instead.
Dec 11