Jump to page: 1 2
Thread overview
What is :-) ?
Nov 20, 2023
Antonio
Nov 20, 2023
evilrat
Nov 20, 2023
Antonio
Nov 20, 2023
evilrat
Nov 20, 2023
Paul Backus
Nov 20, 2023
Paul Backus
Nov 20, 2023
Antonio
Nov 20, 2023
evilrat
Nov 21, 2023
Antonio
Nov 20, 2023
Paul Backus
Nov 21, 2023
Antonio
Nov 20, 2023
Julian Fondren
November 20, 2023

If I run this code

import std.stdio;

void main(){
  auto next = Counter(10);
  next().writeln;
  next().writeln;
  next().writeln;

  // What is "next" function?
  writeln( "'next' is ", next );
  // What is "Counter" function?   This fails
  // writeln( "'Counter' is ", Counter );
}

auto Counter(int nextValue) => () => nextValue++;

Executing this code results in:

10
11
12
'next' is int delegate() pure nothrow @nogc @safe

Now, I uncomment the writeln( "'Counter' is ", Counter ); line and compiler says

/home/antonio/Devel/topbrokers/whatsapp-srv/admin/x.d(12): Error: function `x.Counter(int nextValue)` is not callable using argument types `()`
/home/antonio/Devel/topbrokers/whatsapp-srv/admin/x.d(12):        too few arguments, expected 1, got 0

I understand the problem with UFCS (next is not using UFCS because it is a delegate defined in the own main() function, and ``Counter``` without () is treated as a function call because it is UFCS eligible )

  • What is the way to do writeln work with Counter function the same way it works with next function?
November 20, 2023

On Monday, 20 November 2023 at 08:47:34 UTC, Antonio wrote:

>

Now, I uncomment the writeln( "'Counter' is ", Counter ); line and compiler says

/home/antonio/Devel/topbrokers/whatsapp-srv/admin/x.d(12): Error: function `x.Counter(int nextValue)` is not callable using argument types `()`
/home/antonio/Devel/topbrokers/whatsapp-srv/admin/x.d(12):        too few arguments, expected 1, got 0

I understand the problem with UFCS (next is not using UFCS because it is a delegate defined in the own main() function, and ``Counter``` without () is treated as a function call because it is UFCS eligible )

writeln( "'Counter' is ", Counter );

this code is actually internally looks like this

writeln( "'Counter' is ", Counter() );

if you meant to take the function/delegate and not invoke try &Counter instead, otherwise it expects the parameters.

>
  • What is the way to do writeln work with Counter function the same way it works with next function?

Sorry, that's too confusing and I have no idea what you mean, maybe if you can explain what you are trying to achieve someone might be able to help you.

November 20, 2023

On Monday, 20 November 2023 at 09:11:07 UTC, evilrat wrote:

>

if you meant to take the function/delegate and not invoke try &Counter instead, otherwise it expects the parameters.

If you execute

writeln( "'Counter' is ", &Counter );

It shows the Counter address:

'Counter' is 557F2567F940

Not the function signature like it does with next

I propose a simple change:

void main(){
  auto Counter = (int nextValue) => () => nextValue++;
  auto next = Counter(10);
  writeln( "'next' is ", next );
  writeln( "'Counter' is ", Counter );
}

first writeln shows the signature of next:

'next' is int delegate() pure nothrow @nogc @safe

second writeln shows the address of Counter

'Counter' is 55568953C910
  • Why writeln doesn't treat next and Counter the same way? (I think I understand why, but it shows a "low" level difference of something that syntactically is equivalent)

  • What is the way to show Counter signature using writeln (if possible)?

November 20, 2023

On Monday, 20 November 2023 at 09:44:32 UTC, Antonio wrote:

>
  • Why writeln doesn't treat next and Counter the same way? (I think I understand why, but it shows a "low" level difference of something that syntactically is equivalent)

  • What is the way to show Counter signature using writeln (if possible)?

I found no way to tell compiler that I don't want to call Counter and instead want to take the function itself, but closest thing is just to take the string representation at compile time (same as used in pragma msg) and pass it to writeln instead of Counter.

I guess this is one of these bif oof moments with UFCS, a function returning a (parameterless) function.

Note that in most cases you should never make runtime decisions on .stringof value as it is not standardized.

//pragma(msg, typeof(Counter)); // pure nothrow @safe int delegate() pure nothrow @nogc @safe(int nextValue)
enum f = typeof(Counter).stringof; // same string as above
writeln( "'Counter' is ", f);

Of course this works too
writeln( "'Counter' is ", typeof(Counter).stringof);

November 20, 2023

On Monday, 20 November 2023 at 08:47:34 UTC, Antonio wrote:

>

I understand the problem with UFCS (next is not using UFCS because it is a delegate defined in the own main() function, and ``Counter``` without () is treated as a function call because it is UFCS eligible )

This is not UFCS, it's optional parentheses, which is a separate language feature. (Reminder: UFCS only applies when the callee has the form x.y)

According to the spec, it's disabled for function pointers and delegates due to the potential for ambiguity.

November 20, 2023

On Monday, 20 November 2023 at 08:47:34 UTC, Antonio wrote:

>
  • What is the way to do writeln work with Counter function the same way it works with next function?

writeln(&Counter) should do it.

November 20, 2023

On Monday, 20 November 2023 at 13:25:48 UTC, Paul Backus wrote:

>

On Monday, 20 November 2023 at 08:47:34 UTC, Antonio wrote:

>
  • What is the way to do writeln work with Counter function the same way it works with next function?

writeln(&Counter) should do it.

It does not do the same: It shows an address, not the function signature.

Because I need to understand "why", I propose a second example (with some additional info based on @evilrat proposals :-) ):

import std.stdio;

void main()
{
  auto createCounter = (int nextValue) => (int dummy) => nextValue++;
  auto getNext = createCounter(10);

  writeln( "'getNext' is ", getNext );
  writeln( "'createCounter' is ", createCounter );

  writeln( "'typeof(getNext).stringof' is ", typeof(getNext).stringof );
  writeln( "'typeof(createCounter).string' is ", typeof(createCounter).stringof );
}

The output is

'next' is int delegate(int) pure nothrow @nogc @safe
'createCounter' is 557FFCC00968
'typeof(getNext).stringof' is int delegate(int dummy) pure nothrow @nogc @safe
'typeof(createCounter).string' is int delegate(int dummy) pure nothrow @nogc @safe function(int nextValue) pure nothrow @safe

Why writeln doesn't treat the same way getNext and createCounter?

Because getNext is a delegate and createCounter is a function.

Why this is a function and not a delegate?

auto createCounter = (int nextValue) => (int dummy) => nextValue++;

Syntactically I dont see any difference:

auto name = "expression returning a delegate"

The reason is D compiler takes the decision.

If you make createCounter to depend on an external variable, it will be treated as delegate (because it has context information associated to the function: a closure)

import std.stdio;

void main()
{
  int diff = 1;
  auto createCounter = (int nextValue) => () { scope(exit) nextValue+=diff; return nextValue;};
  writeln( "'typeof(createCounter).string' is ", typeof(createCounter).stringof );
}

Will output that createCounter is a delegate:

'typeof(createCounter).string' is int delegate() pure nothrow @nogc @safe delegate(int nextValue) pure nothrow @safe

What "breaks" my mind is that a compiler decision (treat a piece of code as function or delegate) is not completely transparent causing "side" effects on your code (writeln doesn't work the same way: it shows the delegate signature, but not the function signature).

But the decision of the compiler is predictable and you can argue different effects are not side effects: only something you should pay attention to.

This long and winding road toke me to a third and crazzy question

Is there any way to force D compiler to treat this "createCounter" declaration as delegate instead of function?

  auto createCounter = (int nextValue) => () => nextValue++;
November 20, 2023

On Monday, 20 November 2023 at 16:09:33 UTC, Antonio wrote:

>

Is there any way to force D compiler to treat this "createCounter" declaration as delegate instead of function?

  auto createCounter = (int nextValue) => () => nextValue++;

generally there is a way to tell the compiler specifically that you want a delegate or a function, and additionally there is toDelegate function from std.functional that could be useful in some cases.

https://dlang.org/phobos/std_functional.html#toDelegate

the syntax for specifying delegate/function is smth like this, IIRC return type can be omitted but for reference I write it here as auto.

// this is a function returning a delegate
auto createCounter(int nextValue) => auto delegate() => nextValue++;

// this is a function returning a function
auto createCounter(int nextValue) => auto function() => nextValue++;
November 20, 2023

On Monday, 20 November 2023 at 16:09:33 UTC, Antonio wrote:

>

What "breaks" my mind is that a compiler decision (treat a piece of code as function or delegate) is not completely transparent causing "side" effects on your code (writeln doesn't work the same way: it shows the delegate signature, but not the function signature).

It's certainly surprising that writeln treats function pointers and delegates differently. My guess is that it's because isPointer returns true for functions and false for delegates.

>

Is there any way to force D compiler to treat this "createCounter" declaration as delegate instead of function?

  auto createCounter = (int nextValue) => () => nextValue++;

You can put the delegate keyword in front of the function literal:

auto createCounter = delegate (int nextValue) => () => nextValue++;

This syntax is documented in the spec's section on Function Literals (look at the grammar box and the examples).

November 20, 2023

On Monday, 20 November 2023 at 16:09:33 UTC, Antonio wrote:

>

Why this is a function and not a delegate?

auto createCounter = (int nextValue) => (int dummy) => nextValue++;

Syntactically I dont see any difference:

createCounter is a function, and not a delegate, as it doesn't close over any variables. It returns a delegate as the variable nextValue is closed over. There's no difference in syntax. The difference is however important as an environment must be retained for the closed-over variables and passed along with the pointer, making delegates both more expensive and incompatible with function pointers on the C ABI. Note that f and g are obviously not pointing to the same nextValue:

auto createCounter = (int nextValue) => () => nextValue++;

void main() {
	import std.stdio : writeln;

	auto f = createCounter(5);
	auto g = createCounter(0);
	writeln(f()); // 5
	writeln(g()); // 0
	writeln(f()); // 6
	writeln(g()); // 1
}
>

What "breaks" my mind is that a compiler decision (treat a piece of code as function or delegate) is not completely transparent

D is trying to be convenient (by offering delegates at all, and by making the syntax so light) while offering fine control (by letting you distinguish between function pointers and delegates). It'd be a much simpler language if it dropped one of those aims, but such languages also already exist.

Similarly D also distinguishes between "numbers" (int) and "numbers" (double) and this can also be transparent and also cause 'side effects'. Someone educated in mathematics but not familiar with computing might complain about

void main() {
	import std.stdio : writefln;
	auto n = 1;
	writefln!"%d"(n);
}

breaking when "all I did was bump n by one-tenth, to 1.1".

I'm not trying to be mean with this example, and I don't think it's shameful either to know mathematics but not computing. But D expects you to be familiar with such things for you to not be surprised by how it behaves.

« First   ‹ Prev
1 2