Thread overview
Named constructors
Nov 09
JN
Nov 09
Arafel
Nov 12
IchorDev
6 days ago
Per Nordlöw
6 days ago
Per Nordlöw
November 09

Let's say you want to have an Angle class, which holds radians internally but can be initialized with degrees or radians.

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:

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:

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.

November 09
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.
November 12

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:
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);
November 25
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
6 days ago

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

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

Angle(Radians(0.0));
Angle(Degrees(0.0));

or

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.

6 days ago

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.