Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DIP4242 (?): Argument dependent attributes (ADAs) #198

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Geod24
Copy link
Member

@Geod24 Geod24 commented Dec 11, 2020

This DIP aims to solve the common problem that arise when mixing delegates and attributes.
It describes a language addition which the author feels will be natural to seasoned users
that allow describing the relationship between a function's attribute and its callable(s) parameters.

DIPs/DIP4242.md Outdated Show resolved Hide resolved
// Argument is not a delegate or function pointer
void err2(int arg) @safe(arg);
// Argument is not a delegate or function pointer
void err3(int arg) @safe(0);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The index version is quite confusing because of implicit boolean conversion (this looks like @safe(false) to me)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't think of that. The reason it's in is to support meta programming, where you really don't want to have to mess with identifiers. Sometimes you declare a function with a type tuple and in this case having ADAs support indexes is quite handy.

Copy link

@MoonlightSentinel MoonlightSentinel Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't the same be achieved by allowing @safe(tuple[index]) to refer to the actual parameter inside of a parameter tuple?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how ? Or is tuple a keyword here ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the following function

void foo(T...)(T args) {}

Your current proposal suggests a plain index to specifiy a single parameter:

void foo(T...)(T args) @safe(0) {}

But this would be more expressive:

void foo(T...)(T args) @safe(args[0]) {}

Also a plain index doesn't work well with multiple variadic template parameters:

template bar(A...)
{
	void bar(B...)(A a, B b) @safe(1) {} // Might refer to an element of a or b
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, those are good points. Index based ADAs are a special case for when an argument has no identifier. Since the grammar allows it, I figured there should be a way to handle it.

But even if that's the intended usage, the semantics should be made clearer. And the tuple example is quite neat and more expressive, I agree. Let me think this over for a bit.

This DIP aims to solve the common problem that arise when mixing delegates and attributes.
It describes a language addition which the author feels will be natural to seasoned users
that allow describing the relationship between a function's attribute and its callable(s) parameters.
@Geod24
Copy link
Member Author

Geod24 commented Dec 12, 2020

Thanks for the feedback @MoonlightSentinel, comments addressed.

@Bolpat
Copy link
Contributor

Bolpat commented Dec 30, 2020

I had a similar idea. Your DIP proposes a pure addition to the language which avoids breakage. My approach contains breakage (in theory), but immediately works for all templates that are written already. You may want to have a look at it: #199

As it stands, using @attribute(param) is illegal if param is not a function pointer or delegate. First note that slices and arrays of delegates are encouraged by the language. You should mention and handle them. Similar how unreachable code is an error in non-template code, but commonly found in templates, also for the sake of generic code, it's generally advisable to allow @attribute(param) as long as param exists. If it is not a function pointer or delegate, just ignore it. Generic code might expect param to be function pointer or delegate or a callable object. As it stands, you force generic code to make a needless distinction.

@Geod24
Copy link
Member Author

Geod24 commented Dec 30, 2020

As it stands, using @attribute(param) is illegal if param is not a function pointer or delegate. [...] As it stands, you force generic code to make a needless distinction.

To address this whole paragraph, my expectation is that generic code with use @attribute(*) which uses all matching parameter, and is explicitly described as supporting the case of no parameter being a delegate / function for this exact reason. The only case where users would want to use @attribute(param) with generic code would be if one of the param is called but not the other, which I suspect will almost never happen. TL;DR generic code should use @attribute(*).
But if such a case happens, generic code is encouraged to use the index variant, which is less error prone.

This is of course my intuition / expectation as designer of this feature and first user, and I suspect the best way to convince people is to provide a working PoC, which I'm workin on.

I'll also check your DIP shortly.

@rikkimax
Copy link
Contributor

rikkimax commented Jun 6, 2022

This is going to be very nice to have but two things:

  1. opApply needs more examples. I have a mixin template to generate opApply's to a templated implementation method to accept any combination of attributes on both the delegate and method.
  2. This isn't specific to delegates, it's specific to the pointer to a function regardless of how fat it is. It may be better to write it as a function pointer and only upfront and where required say it also applies to delegates.

@Bolpat
Copy link
Contributor

Bolpat commented Jul 21, 2022

It might be a late addition, but @tgehr convinced me in several private and public conversations that it’s much more orthogonal and valuable to add “attribute variables”, i.e. a something that (for it to be relevant) occurs two or more times in place of an attribute. As in mathematics, is the same everywhere. D already has something like this with inout, which is a hard-coded “type qualifier variable”, i.e. when a function is called that includes inout in its declaration, it is replaced by immutable, const or nothing (mutable). By hard-coded I mean that you cannot change its name and that there is no way to have multiple “type qualifier variables” in one declaration: You cannot express:

inout₁(int) f(inout₂(bool)[] input, out inout₂(bool)* output) inout₁;

I fear something like this is possibly happening with ADAs. Instead of referring to parameter names (or indices), maybe let e.g. @safe(id) be a general attribute variable, a @safe-ty if you like, that can take the values @safe and @system (@trusted is the same as @safe from the perspective of the caller). What ADAs cannot express, but attribute variables can, would be something like this:

void delegate() @safe(a) hof(void delegate() @safe(a) callback) @safe;

It says that hof is a @safe function that returns a @safe function if and only if its parameter is @safe. Here, a is just an identifier to distinguish it from others.
Closing to ADAs, as syntactic sugar, if a parameter p has a single function pointer or delegate type in its type expression – its type could be void function()[] which is a slice of function pointers, not just a function pointer –, then this function pointer or delegate type implicitly carries @safe(p), pure(p) etc. – meaning that

void hof(void function()[] callbacks) @safe(callbacks);

is equivalent to

void hof(void function() @safe(a)[] callbacks) @safe(a);

Apart from the sugaring, using an attribute variable only once should be an error. If you need the parameter names of packs or unnamed ones, there is __traits(parameters)[i] right there for you to get the i-th parameter name. Today, technically, __traits(parameters) cannot be used in a function’s signature or function template’s contract, but it’s not a big change allowing it.

@schveiguy
Copy link
Member

Just was talking about this on discord, we need a solution for something like:

void foo(T)(T val, void delegate() dg) ???
{
   val.member();
   dg();
}

Basically, foo is a template, you want to infer attributes based on the usage of T, but you also want to color the attributes from the call site based on the non-template dg. How does one specify this?

An alternative that I have been thinking about, what if you just use @called as a delegate/function pointer attribute? e.g. for your first example:

// you wrote
void toString (scope void delegate(in char[]) sink) const
    @safe(sink) pure(sink) nothrow(sink) @nogc(sink)

// I'm thinking:
void toString (scope void delegate(in char[]) @called sink) const
    @safe pure nothrow @nogc;

I think this works, without double-underscores, because nothing custom can go in that position.

@Geod24
Copy link
Member Author

Geod24 commented Oct 5, 2022

you want to infer attributes based on the usage of T

And you could infer @nogc(sink) et al just the same, couldn't you ?

what if you just use @called

I'll have to think about it, but I think it's not granular enough. Definitely need to add a section in the DIP about that idea, though.

@Bolpat
Copy link
Contributor

Bolpat commented Oct 6, 2022

Shouldn’t the @called attribute be a parameter attribute (along the lines of scope)? A function attribute feels wrong. It’s not really part of the function type.

@schveiguy
Copy link
Member

Maybe, but also, you are doing funky things to connect the attributes of the argument to the attributes of the function itself. i.e. it's like a placeholder for the argument that you pass in.

If you put it as a parameter attribute, it probably has to be @__called. That also works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants