Thread overview
Is there a way to enforce UFCS?
Jan 04, 2023
thebluepandabear
Jan 04, 2023
Ali Çehreli
Jan 04, 2023
bauss
Jan 04, 2023
thebluepandabear
Jan 05, 2023
Dom DiSc
Jan 05, 2023
H. S. Teoh
Jan 05, 2023
thebluepandabear
Jan 06, 2023
Salih Dincer
Jan 06, 2023
Salih Dincer
January 04, 2023

Say you have the following class which represents a dog 🐶:

class Dog {
    @property {
        string name();

        void name(string name) {
            _name = name;
        }
    }

    private {
        string _name;
    }
}

And you have the following code with constructs a Dog object:

void main() {
    Dog d = new Dog();

    d.name = "Poodle";
    writeln(d.name);
}

In the code we can see that we have utilized UFCS (universal function call syntax) to set the properties for the object. This feature is great. We have also used D's @property annotation which gives us some other advantages that you can see in the documentation.

The issue I have is that UFCS is not enforced, which I thought would be a rather good use for the @property annotation. This means that we can do the following in our code:

void main() {
    Dog d = new Dog();

    d.name("poodle");
    writeln(d.name());
}

I prefer the UFCS version over the non-UFCS version since it is more clear that it is a property and it matches closely with the official D style guide.

I am disappointed that @property does not enforce UFCS, as I believe that it would add to its usefulness. Sometimes throughout my codebase I get confused and write properties in non-UFCS syntax, which bugs me a bit.

My question is: is there a way to enforce UFCS-syntax?

January 03, 2023
On 1/3/23 19:42, thebluepandabear wrote:

>      @property {

As your post proves, that feature is at most half-baked and is discouraged. Today, there is just one known obscure effect of using it.

>          void name(string name) {
>              _name = name;
>          }

>      d.name = "Poodle";

> In the code we can see that we have utilized UFCS (universal function
> call syntax)

UFCS is for calling free-standing functions as if they are member functions. Since your example already uses member functions, this feature is not UFCS. And I don't think it has a name.

It is always possible to pass a single-argument with the assignment syntax:

void foo(int i) {}

void main() {
    foo = 42;
}

Pretty wild! :) But that's what makes your assignment above work. (Not UFCS.)

> not enforced [...] we can
> do the following in our code:

>      d.name("poodle");

I don't see a problem with that. :)

> I am disappointed that `@property` does not

Many people are disappointed that @property is pretty much useless.

> is there a way to enforce

D gives us the tools to do that but it's not trivial. The function can return an object that represents a variable (member variable or not). And an assignment to that representative object can set the actual variable.

However, I tried to achieve it with an intermediary object but failed because the same ="Poodle" syntax broke it and demanded that we type the empty parenthesis. So, I think what you want does not exist.

// I have a feeling something similar exists in Phobos
// but I could not find it.
//
// This is a reference to any variable.
struct MyRef(T) {
    T * ptr;

    void opAssign(T value) {
        *ptr = value;
    }

    void toString(scope void delegate(in char[]) sink) const {
        sink(*ptr);
    }
}

// This is a convenience function template so that
// the users don't have to say e.g. MyRef!string
// themselves. (See name() below.)
auto myRef(T)(ref T var) {
    return MyRef!T(&var);
}

class Dog {
    @property name() {
        return myRef(_name);
    }

    private {
        string _name;
    }
}

void main() {
    Dog d = new Dog();


    // Great: The following won't work.
    // d.name("poodle");


    // However, now there is the empty parenthesis. :(
    d.name() = "Poodle";

    import std.stdio;
    writeln(d.name);
}

Ali

January 04, 2023

On Wednesday, 4 January 2023 at 03:42:28 UTC, thebluepandabear wrote:

>

...

My question is: is there a way to enforce UFCS-syntax?

None of your code actually uses UFCS.

This is UFCS:

class Foo {
  int bar;
}

void setBar(Foo foo, int value) {
  foo.bar = value;
}

void main() {
  foo.setBar(100); // UFCS
  setBar(foo, 100); // Non-UFCS (Above expands to this)
}

This is not UFCS but just class method calling:

class Foo {
  int bar;

  void setBar(Foo foo, int value) {
    foo.bar = value;
  }
}

void main() {
  foo.setBar(100); // Not UFCS - just method call to the class
  foo.setBar = 100; // Not UFCS - simply a setter function call (equal to the above)
  setBar(foo, 100); // Error
}

Also note that @property doesn't really do anything now and there's even talk about deprecating it. Althought I personally still use it, then it doesn't have much of a function and none of your code is affected if you remove it.

January 04, 2023

On Wednesday, 4 January 2023 at 14:21:46 UTC, bauss wrote:

>

On Wednesday, 4 January 2023 at 03:42:28 UTC, thebluepandabear wrote:

>

...

My question is: is there a way to enforce UFCS-syntax?

None of your code actually uses UFCS.

This is UFCS:

class Foo {
  int bar;
}

void setBar(Foo foo, int value) {
  foo.bar = value;
}

void main() {
  foo.setBar(100); // UFCS
  setBar(foo, 100); // Non-UFCS (Above expands to this)
}

This is not UFCS but just class method calling:

class Foo {
  int bar;

  void setBar(Foo foo, int value) {
    foo.bar = value;
  }
}

void main() {
  foo.setBar(100); // Not UFCS - just method call to the class
  foo.setBar = 100; // Not UFCS - simply a setter function call (equal to the above)
  setBar(foo, 100); // Error
}

Also note that @property doesn't really do anything now and there's even talk about deprecating it. Althought I personally still use it, then it doesn't have much of a function and none of your code is affected if you remove it.

Yeah I got mixed up.

I think a good use of @property is for code clarity, it makes it clear which parts of your code should be treated as properties.

January 05, 2023

On Wednesday, 4 January 2023 at 14:21:46 UTC, bauss wrote:

>
class Foo {
  int bar;

  void setBar(Foo foo, int value) {
    foo.bar = value;
  }
}

void main() {
  foo.setBar(100); // Not UFCS - just method call to the class
  foo.setBar = 100; // Not UFCS - simply a setter function call (equal to the above)
  setBar(foo, 100); // Error
}

I think this is really another usecase for @property: we should forbid the function call syntax for them (so one needs to use them like a variable).
This is useful to enable library authors to enforce this, so that if a property is replaced by a variable (e.g. during refactoring), it's not a braking change for the users of the library.
Else it could be that setters or getters are directly called, which would not compile anymore with a variable (that doesn't have getters or setters).

@properties are intended to be used like variables - the only differences (and the reason why they exist) is the read or write protection they provide, and that they may be calculated on the fly (not stored in memory at all). That they are realized with a construct that looks similar to (a pair of) functions should be completely transparent for the user of a library.

Properties are not functions. If you want a function, use a function. If @properties would be the same as functions, they are superfluous garbage. Either make something useful out of them or remove them.

January 05, 2023
On Thu, Jan 05, 2023 at 02:32:17PM +0000, Dom DiSc via Digitalmars-d-learn wrote: [...]
> I think this is really another usecase for @property: we should forbid the function call syntax for them (so one needs to use them like a variable).
[...]
> Properties are not functions. If you want a function, use a function. If @properties would be the same as functions, they are superfluous garbage. Either make something useful out of them or remove them.

We have been talking about deprecating and removing @property for years now.  Somebody just has to bite the bullet and push it through the deprecation process...

... OR come up with a DIP that implements @property in a sane, fully worked out way, not the half-hearted, incomplete, leaky implementation that it is today.

//

In my own code, I've stopped bothering with @property for the most part. Parentheses are optional for argumentless functions in general anyway, so there's really no need to write @property on anything. This works:

	struct S {
		private int _x;
		int x() { return _x; }
	}

	S s;
	int y = s.x;

The only significant thing @property does right now is to add confusion when the unary & operator is used or when the property function returns a delegate.  Not worth the trouble, I say.  Just don't use @property at all, plain old member functions work just fine.


T

-- 
Some ideas are so stupid that only intellectuals could believe them. -- George Orwell
January 05, 2023
>

them or remove them.

I agree, forbidding function call syntax would be a great usecase for @property.

It will probably never get implemented though.

January 06, 2023

On Thursday, 5 January 2023 at 23:05:17 UTC, thebluepandabear wrote:

> >

them or remove them.

I agree, forbidding function call syntax would be a great usecase for @property.

It will probably never get implemented though.

In older versions, it worked when printing values ​​with writeln. But due to an error in formattedWrite, the program breaks. So I stopped using @property. For example, you can't see the difference in:

struct Funy(T)
{
  import std.conv : to;
  this(X)(X x) {
    value = x.to!T;
  }
  T value;
  alias value this; // [a] getter
  //@property
  T opAssign(T x) { // [b] setter
    return value = x;
  }
  alias opCall = opAssign; /* hack: `opAssign` methods
                      are not used for initialization,
                      but for subsequent assignments
  [c] incrementor:         */
  //@property
  T opOpAssign(string op: "+", X)(X x) {
    return value = value + x.to!T;
  }
}

import std.stdio;
void main()
{
  auto funy = Funy!uint(20);
  funy.value.writeln; // 20
  funy = 10;
  funy.value.writeln; // 1

  class Fun
  {
    Funy!int n;
    this(int i)
    {
      n = i; // or:
      n(i + 1);
    }
  }

  auto fun = new Fun(-2);
  fun.n.writeln; // -1
  fun.n += 19.999;
  fun.n.writeln; // 18
}

Let's continue the fun...

struct Funy(T)
{
  this(T x) { value = x; }
  T value;
  alias opCall this;

  //@property:
  T opCall(T n) { return value = n; }
  T opCall() { return value; }
}

import std.stdio;
void main()
{
  auto funy = Funy!uint(20);
  funy.value.writeln; // 20
  funy = 10;
  funy.value.writeln; // 1

  class Fun
  {
    Funy!int n;
    this(int i) {
      n = i; // or:
      n(i + 1);
    }
  }

  auto fun = new Fun(-2);
  fun.n.writeln; // -1
  fun.n = 20;
  fun.n.writeln; // 0
} /* PRINTS:
20
10
Funy!int(-1)
Funy!int(20)

If you don't want to get the above output you should use the previous example. But don't forget to connect alias and opCall. For example, you can use @property in version 2.0.83 without all the fanfare.

SDB@79

January 06, 2023

On Friday, 6 January 2023 at 15:31:09 UTC, Salih Dincer wrote:

>

If you don't want to get the above output you should use the previous example. But don't forget to connect alias and opCall. For example, you can use @property in version 2.0.83 without all the fanfare.

I forgot one thing: if you implement getter/setter like below use inout as well.

Actually, this must be a bit of a bug and neither I nor anyone else reported it! Ok they will say don't use @property. But this time the screen output will be like typeid. If you don't want that to happen, you have to use inout in getter functions.

struct Funy(T)
{
  this(T x) {
    value = x;
  }

  T value;
  alias opCall this;

  @property:
  T opCall(T n)
  {
    return value = n;
  }

  T opCall() inout
  {
    return value;
  }
}

SDB@79