May 31, 2022

On 5/31/22 8:29 PM, Steven Schveighoffer wrote:

>

However, thinking about it more, we do allow function prototypes as local functions. But I can't figure out a way to actually define them, aside from using pragma(mangle).

I think there's an opportunity here where we can allow function prototypes, allow definitions later, and not break existing code.

Oh my, I just realized, a function prototype that's later defined could end up using stack frame data that doesn't exist. This is the reason for not allowing prototypes or forward reference functions:

void foo()
{
  void bar();
  bar(); // would affect x before it exists
  int x = 5;
  void bar() { ++x; }
}

So just use a static struct if you want mutual recursion.

-Steve

June 01, 2022
On 01.06.22 02:38, Steven Schveighoffer wrote:
> 
> So just use a static struct if you want mutual recursion.

A static struct defeats much of the purpose of local functions.
May 31, 2022

On 5/31/22 9:20 PM, Timon Gehr wrote:

>

On 01.06.22 02:38, Steven Schveighoffer wrote:

>

So just use a static struct if you want mutual recursion.

A static struct defeats much of the purpose of local functions.

Sorry, I meant struct static functions. A static struct is OK if you just want recursive behavior, but don't need to access the stack frame. You can also use normal member functions inside a normal struct if you do need the stack frame.

I use it when I need to implement algorithms that are recursive, but I want to separate out the pieces to multiple functions.

-Steve

June 01, 2022

On Tuesday, 31 May 2022 at 22:48:55 UTC, Don Allen wrote:

>

My concern is not "solving this". My concern is whether the language is clean and consistent so I can have mental model of it I can rely upon, rather than constantly searching through documentation to learn how a particular special case is handled. I also care a lot about writing readable code, and about being able to limit the visibility of variables to just the scope that needs them and no more. The workarounds you cite frankly feel like hacks to me, ugly ways of working around problems in the language design.

/Don

>

-Steve

IMO this is completely internally consistent. Your mental model of a function should be a tree of scopes; your mental model of a module should be a lazily resolved key-value store. They're just fundamentally different things.

You can make function scopes behave similar to global scopes, ie. void delegate() foo; void bar() { foo(); } foo = { ... };, but then you just move the compiletime error to runtime: ie. void delegate() foo; foo(); You're basically manually implementing dynamic scoping. But D is lexically scoped for functions, not just with nested functions but with every language element. Modules are the exception - or rather, modules are just a fundamentally different thing.

June 01, 2022

On Wednesday, 1 June 2022 at 07:54:04 UTC, FeepingCreature wrote:

>

implementing dynamic scoping. But D is lexically scoped for functions, not just with nested functions but with every language element.

The functions foo and bar are in the same scope, checking for initialization before use is not strictly related. Also goto can jump to a forward label, so C allows some forward references.

June 01, 2022

On Wednesday, 1 June 2022 at 08:22:24 UTC, Ola Fosheim Gr wrote:

>

On Wednesday, 1 June 2022 at 07:54:04 UTC, FeepingCreature wrote:

>

implementing dynamic scoping. But D is lexically scoped for functions, not just with nested functions but with every language element.

The functions foo and bar are in the same scope, checking for initialization before use is not strictly related. Also goto can jump to a forward label, so C allows some forward references.

Well, I guess you can say that every new name creates a new scope as it shadows the outer scope in order. Anyway, a prototype isn't a bad trade-off.

(Btw IIRC «dynamic scope» is a completely different concept, that means the lookup is done down the call-graph. E.g. if function f() calls g() and f() has xthen g() will access f()'s x when mentioning x.)

June 01, 2022

On Wednesday, 1 June 2022 at 07:54:04 UTC, FeepingCreature wrote:

>

On Tuesday, 31 May 2022 at 22:48:55 UTC, Don Allen wrote:

>

My concern is not "solving this". My concern is whether the language is clean and consistent so I can have mental model of it I can rely upon, rather than constantly searching through documentation to learn how a particular special case is handled. I also care a lot about writing readable code, and about being able to limit the visibility of variables to just the scope that needs them and no more. The workarounds you cite frankly feel like hacks to me, ugly ways of working around problems in the language design.

/Don

>

-Steve

IMO this is completely internally consistent. Your mental model of a function should be a tree of scopes; your mental model of a module should be a lazily resolved key-value store. They're just fundamentally different things.

You can make function scopes behave similar to global scopes, ie. void delegate() foo; void bar() { foo(); } foo = { ... };, but then you just move the compiletime error to runtime: ie. void delegate() foo; foo(); You're basically manually implementing dynamic scoping. But D is lexically scoped for functions, not just with nested functions but with every language element. Modules are the exception - or rather, modules are just a fundamentally different thing.

I don't disagree with your description of D -- that module-level scopes are different than those at function level. You omitted struct-level scoping, which is similar to how modules are handled. And your discussion of how to make function-level scoping behave like module-level scoping ignores what I have previously written. I know of the availability of these workarounds and to me they are just band-aids on an odd language inconsistency.

I will say this once more and I'm done: function-level scoping in D is different from the other two D cases and different from other prominent lexically scoped languages. I question why that is. You assert that things are "internally" consistent. That may be so, but I would argue that that is completely irrelevant. What matters is external consistency, what the programmer sees.

June 01, 2022
On 01.06.22 09:54, FeepingCreature wrote:
> On Tuesday, 31 May 2022 at 22:48:55 UTC, Don Allen wrote:
>> My concern is not "solving this". My concern is whether the language is clean and consistent so I can have mental model of it I can rely upon, rather than constantly searching through documentation to learn how a particular special case is handled. I also care a lot about writing readable code, and about being able to limit the visibility of variables to just the scope that needs them and no more. The workarounds you cite frankly feel like hacks to me, ugly ways of working around problems in the language design.
>>
>> /Don
>>
>>>
>>> -Steve
> 
> IMO this is completely internally consistent. Your mental model of a function should be a tree of scopes; your mental model of a module should be a lazily resolved key-value store. They're just fundamentally different things.
> 
> You can make function scopes behave similar to global scopes, ie. void delegate() foo; void bar() { foo(); } foo = { ... };, but then you just move the compiletime error to runtime: ie. void delegate() foo; foo(); You're basically manually implementing dynamic scoping. But D is lexically scoped for functions, not just with nested functions but with every language element. *Modules* are the exception - or rather, modules are just a fundamentally different thing.

The simple fact is that the current behavior is less useful than what we could very easily have. There is no need at all to solve this with function pointers the way you describe, just allow forward references and overloading between local functions that are declared next to each other. All later stages of the compilation can very easily handle this, it's just a slightly different order of inserting stuff into scopes and analyzing it.
June 01, 2022

On Wednesday, 1 June 2022 at 13:48:48 UTC, Timon Gehr wrote:

>

There is no need at all to solve this with function pointers the way you describe, just allow forward references and overloading between local functions that are declared next to each other.

The most sensible is to let local functions be syntax sugar for lambdas. Just make delegate variables assign-once and initialize-before-use.

The core D semantics can be made quite simple with a bit of effort. I hope SDC thinks about how to boil the D semantics down to a bare minimum…

June 02, 2022

On Tuesday, 31 May 2022 at 22:48:55 UTC, Don Allen wrote:

>

Well, I would argue that that kind of thinking leads to a language that is a collection of special cases, rather than a language built on a core set of principles consistently applied.

While I agree that sometime, D takes the road of many special cases, if you take a step back, you'll that this really doesn't help your case, to the contrary. You are arguing for special casing function scope to allow out of order symbol resolution in special circumstances.