September 28, 2014
On 2014-09-28 00:56, Walter Bright wrote:

> I see no gain from that syntax.

It's very convenient especially when using range based programming.

-- 
/Jacob Carlborg
September 28, 2014
Walter Bright:

> A feature without a solid rationale is no good in spite of how many votes it has.

I agree that the number of votes alone means very little. But perhaps those persons have reasons.
(If votes from people are not important, then better to remove this feature from Bugzilla.)


> D has a nice inline lamda syntax, which was needed. It is not needed for separate functions.

The lambda syntax for function/class methods is not needed, but it's handy and it's sufficiently natural, expecially for many little methods. It's not an important feature, and there are D features that I need far more than this (like syntax to unpack tuples), but some people like it. Apparently C#6 designers agree with that, search for "Expression-bodied members" here:

http://www.dotnetcurry.com/showarticle.aspx?ID=1042

The C#6 syntax is the same as the proposed one for D:

class Rectangle(int width, int height)
{
    public int Area => width * height;
}


> If the function is small enough that parameter setup time is significant, it is likely to be inlined anyway which will erase the penalty.

This is the theory :-) Sometimes I have seen this not to be true.


> Then you really aren't encapsulating the globals the function uses, anyway, and the feature is useless.

The point of having an @outer() is to enforce what module-level names a function is using and how it is using them (in/out/inout), so it's useful for _impure_ functions. If your functions can be annotated with "pure" you don't need @outer much.


> Being global variables, they are accessible from everywhere :-) which is why they're an encapsulation problem in the first place.

In D we have modules, so variables are module-level, or they are imported from other modules (unfortunately names in a module are public on default when you import the module. I'd like the opposite, to be private to the module, and importable only if they are tagged with "public").
The problem with module-level variables is that sometimes I am using by mistake a global variable when I am instead trying to use a local one. Or on the opposite I am using a local variable by mistake when I'd like to use a module-level one. An attribute like "pure" disallows the access to global mutables, so it solves only half of the problem. Generally knowing exactly what module-level variables (and how) a function is using is very handy to understand the purpose and working of the function itself. So I see @outer() as a tool for code understanding, even legacy code because I can add @outer() to old code written by other persons.


>> - @outer is more DRY, because you don't need to specify the type of the global
>> variable received by ref, you just need to know its name.
>
> That's why gawd invented templates.

Templates are a blunt tool to solve the problems faced by @outer(). @outer() allows you to specify for each variable if it's going to just be read, written, or read-written. And @outer() is not named @globals() because it works for inner functions too, it allows to control and specify the access of names from the outer scopes, so an inner impure nonstatic function can use @outer() to specify what names it can use from the scope of the outer function:

void foo() {
  int x;
  @outer(in x) void bar() {
    writeln(x);
  }
  bar();
}


> You can always wrap it with a template and pass the global by alias.

I think I've never done this. To be tried.

Bye,
bearophile
September 28, 2014
On Saturday, 27 September 2014 at 22:06:00 UTC, H. S. Teoh via Digitalmars-d wrote:
> On Sat, Sep 27, 2014 at 02:51:14PM -0700, Walter Bright via Digitalmars-d wrote:
> [...]
>> The only interesting thing is he describes a way that functions (and
>> blocks) can specify what global data they access, and then have the
>> compiler issue errors for accessing any other global data. He has a
>> way to do that both locally and transitively.
>
> That sounds like PHP. *shudder*
>
>
>> However, the same effect can be achieved via pure annotations and
>> passing the globals needed by reference as function parameters.
> [...]
>
> I'm a fan of grouping related globals into structs with module level
> instances, and passed by reference to functions that need those specific
> globals. Truly-global globals are nasty, as are open sets of globals
> where you either don't access a global, or you (potentially) access
> *all* globals with no finer access granularity. (I had to debug C code
> that did that once... boy it was mind-bending when every function call
> could potentially arbitrarily change the global state.)
>
>
> T

A fair amount of HPC code is still written in Fortran with a variables.f90 file that holds most program state as globals. Ick.
September 28, 2014
On 9/28/2014 2:16 AM, bearophile wrote:
> Walter Bright:
>> If the function is small enough that parameter setup time is significant, it
>> is likely to be inlined anyway which will erase the penalty.
> This is the theory :-) Sometimes I have seen this not to be true.

Inlining is not a random thing. If there's a case that doesn't inline, ask about it.


>> Then you really aren't encapsulating the globals the function uses, anyway,
>> and the feature is useless.
>
> The point of having an @outer() is to enforce what module-level names a function
> is using and how it is using them (in/out/inout), so it's useful for _impure_
> functions. If your functions can be annotated with "pure" you don't need @outer
> much.

@outer implies purity with a list of exceptions.


>>> - @outer is more DRY, because you don't need to specify the type of the global
>>> variable received by ref, you just need to know its name.
>>
>> That's why gawd invented templates.
>
> Templates are a blunt tool to solve the problems faced by @outer(). @outer()
> allows you to specify for each variable if it's going to just be read, written,
> or read-written.

Again, template functions do that rather nicely, and no type is required.


> And @outer() is not named @globals() because it works for inner
> functions too, it allows to control and specify the access of names from the
> outer scopes, so an inner impure nonstatic function can use @outer() to specify
> what names it can use from the scope of the outer function:
>
> void foo() {
>    int x;
>    @outer(in x) void bar() {
>      writeln(x);
>    }
>    bar();
> }

This I find to be a bit more lame, because the declarations used will be right there. But if you really wanted to do it,

 void foo() {
    int x;
    static void bar(int x) {
      writeln(x);
    }
    bar(x);
 }


September 28, 2014
 His entire reason for not using C++ lambda, and wanting local functions instead, is faulty

His reason: I heard they can perform heap allocation.

Reality: No they do not perform heap allocation, that would only happen if you stuffed it into an std::function


September 28, 2014
Walter Bright:

> Inlining is not a random thing. If there's a case that doesn't inline, ask about it.

Even when you use annotations like "forced_inline" and the like you can't be certain the compiler is doing what you ask for. I have seen several critical template functions not inlined even by ldc2 (dmd is usually worse). And if your function is in a pre-compiled object where the source code is not available, inlining doesn't happen.


> @outer implies purity with a list of exceptions.

Right, an empty "@outer()" equals to the D "weakly pure". But if I put those exceptions then I am essentially stating that a function is not pure. So you usually don't put @outer() on pure functions.


> Again, template functions do that rather nicely, and no type is required.

I think templates are blunt tools for this purpose. But I will try to use them for this purpose, to see how they fare. I think I have not seen people use templates for this purpose, so I think it's not very natural.


>> void foo() {
>>   int x;
>>   @outer(in x) void bar() {
>>     writeln(x);
>>   }
>>   bar();
>> }
>
> This I find to be a bit more lame, because the declarations used will be right there.

Yes, and a module-level variable can be defined the line before the function definition. Or it can be far from it (it can even be from an imported module). The same is true for the variables defined in the outer function. That's why I have named it @outer instead of @globals. Even a large function is usually smaller than a whole module (and modern coding practices suggest to avoid very large functions), so the variable definition should be closer (and in D it must be lexically before the definition of the inner function), so the problems with module-level variables is bigger, but it's similar.


> But if you really wanted to do it,
>
>  void foo() {
>     int x;
>     static void bar(int x) {
>       writeln(x);
>     }
>     bar(x);
>  }

As you said, the same is possible with global variables, you can often pass them as arguments, by value or by reference. I agree that the case of using @outer() for inner functions is weaker.

Bye,
bearophile
September 28, 2014
On 9/28/2014 10:57 AM, po wrote:
>   His entire reason for not using C++ lambda, and wanting local functions
> instead, is faulty
>
> His reason: I heard they can perform heap allocation.
>
> Reality: No they do not perform heap allocation, that would only happen if you
> stuffed it into an std::function


D's will also do heap allocation, but only if you take the address of the local function.
September 28, 2014
On Sun, Sep 28, 2014 at 10:19:36AM -0700, Walter Bright via Digitalmars-d wrote: [...]
> Inlining is not a random thing. If there's a case that doesn't inline, ask about it.
[...]

This is not directly related to this thread, but recently in a Phobos PR we discovered the following case:

	// This function gets inlined:
	int func1(int a) {
		if (someCondition) {
			return value1;
		} else {
			return value2;
		}
	}

	// But this one doesn't:
	int func2(int a) {
		if (someCondition) {
			return value1;
		}
		return value2;
	}

IIRC Kenji said something about the first case being convertible to an expression, but the second can't. It would be nice if inlining worked for both cases, since semantically they are the same.


T

-- 
Famous last words: I *think* this will work...
September 29, 2014
On 9/28/2014 1:25 PM, H. S. Teoh via Digitalmars-d wrote:
> This is not directly related to this thread, but recently in a Phobos PR
> we discovered the following case:
>
> 	// This function gets inlined:
> 	int func1(int a) {
> 		if (someCondition) {
> 			return value1;
> 		} else {
> 			return value2;
> 		}
> 	}
>
> 	// But this one doesn't:
> 	int func2(int a) {
> 		if (someCondition) {
> 			return value1;
> 		}
> 		return value2;
> 	}
>
> IIRC Kenji said something about the first case being convertible to an
> expression, but the second can't. It would be nice if inlining worked
> for both cases, since semantically they are the same.

https://issues.dlang.org/show_bug.cgi?id=7625

October 01, 2014
Max Klyga:

> https://www.youtube.com/watch?v=TH9VCN6UkyQ

A third talk (from another person) about related matters:
https://www.youtube.com/watch?v=rX0ItVEVjHc

He doesn't use RTTI, exceptions, multiple inheritance, STL, templates, and lot of other C++ stuff. On the other hand he writes data-oriented code manually, the compiler and language give him only very limited help, and the code he writes looks twiddly and bug-prone. So why aren't they designing a language without most of the C++ stuff they don't use, but with features that help them write the data-oriented code they need?

Bye,
bearophile