29.09.2014 18:17, "Marc =?UTF-8?B?U2Now7x0eiI=?=
* What is ElementType!(ByLineImpl!(char, "\n")) in the example from the
wiki page [1]?
OK, I think I have an idea, but it's not overly elegant.
First of all, I would like to note that the same issue exists
with, for example, findSubstring function (from "scope with
owners" section), which does not have a definite return type
(so it's unclear what ReturnType!findSubstring should be).
Indeed. It's everywhere that an owner depends on another function
argument (including `this`), but AFAICS nowhere else.
Now, an idea that I have is that bare scope should be just a
syntactic sugar for self-owned scope; i.e., `scope(int*) x;`
would be just a short way of writing `scope!x(int*) x;`. This
would imply that every scoped variable has its own unique type,
which is probably not that terrible, considering that they
can't be assigned freely to each other either way. This is also
somewhat more natural, because it means that there is no such
thing as just "scoped" variable, it is always connected to a
particular lifetime.
I've already suggested this as an implementation detail.
---
string findSubstring(scope(string) haystack,
scope(string) needle)
---
would be equivalent to
---
string findSubstring(scope!haystack(string) haystack,
scope!needle(string) needle)
---
so each argument is self-owned and all ownership information is
lost (but function is still callable with scoped arguments,
because scope narrowing is allowed).
To preserve ownership information, which is encoded in an
---
scope!haystackOwner(string)
findSubstring(alias haystackOwner)
(scope!haystackOwner(string) haystack,
scope(string) needle)
---
So, findSubstring *still* doesn't have a definite return type,
but now it's because it is a function template, not a function.
This of course has the unfortunate side effect of incredible
template bloat: For any distinct passed argument (which in
practice means for almost every call), we'd get a new template
instance. IMO this is not acceptable.
As for passing unscoped strings to findSubstring, I see two
1) Declare that all unscoped references are implicitly
convertible to scope!GC or something like that (and this
conversion is used in such cases). This one is probably better.
OTOH it would preclude automatic demoting of GC allocations to
stack allocations. And it would marry us to the GC is "default"
allocation strategy, which we might want to move away from
(probably in favor of generic allocators changeable at any point
in time).
We also need to allow borrowing of temporaries if we want to have
rvalue references. And this runs into problems if we are dealing
with scoped non-references, for which I'm seeing more use cases
now than just file descriptors (in particular reference
counting). In short, I'd like to avoid it.
2) Require a separate overload for an unscoped string.
Ugly, of course, and I believe it's not necessary.
Now, to address ElementType problem, I would like to reconsider
`this` lifetime. Since
---
struct S {
void f() {}
}
---
is more or less equivalent to
---
// not a valid D code
struct S {}
void f(ref S this) {}
---
(not strictly equivalent, of course, but the general idea is
like that),
`this` inside a method body would behave like a ref parameter
and have approximately the same lifetime as normal parameters.
---
@property scope!(const this)(Char[]) front() const
---
would become illegal, since the return value is declared as
---
@property scope!(const this)(Char[])
front(alias thisOwner)() const scope!thisOwner
---
would work just fine, because it explicitly propagates current
object's scope (the return type seems the same, but `this`
itself now has a different type). The downside is that `front`
is now callable only for scoped variables (it seems
inappropriate to convert structs to scope!GC).
And of course, again, the template bloat :-( This time, a new
instance of `front` for every instance of the range.
Now, ByLineImpl!(char, "\n") wouldn't be an input range,
because front would not be defined in an unscoped case, and
`scope ByLineImpl!(char, "\n")` is not a type (because bare
scope would be purely a syntactic sugar in declarations), so
not a range.
---
scope(ByLineImpl!(char, "\n")) x = ...;
---
typeof(x) would be scope!x(ByLineImpl!(char, "\n")), and
ElementType!(typeof(x)) would be scope!(const x)(char[]).
As I have said in the beginning, not too elegant, but I think
it may work. As a bonus, this is a little bit more
straightforward and requires less special-casing from the
compiler side: has only one scope type instead of two and no
magic tricks with scoped return values. The only problems that
I can see right away are that the code now is a little verbose,
and that 'front' is restricted to scoped variables in a new
version.
Any thoughts?
I think the key is in separating the scope attribute and the
owner. The former needs to be part of the type, the latter
doesn't. In this vein, it's probably a good idea to restrict the
`scope!owner` syntax to function signatures, where it may only
refer to other parameters and `this`. The use cases for it
elsewhere are very marginal (if they exist at all).
This naturally makes the return type of `findSubstring()` just
`scope(string)`; declaring a variable of it simply works:
typeof(findSubstring("", "")) s = findSubstring("Hello,
world", "world");
is equivalent to:
scope(string) s = findSubstring("Hello, world", "world");
This is a valid assignment, and owner propagation would even take
care of preserving the owners (though only on declaration, but
that's natural because the owners are only known at the call
site):
string haystack, needle;
scope(string) s = findSubstring(haystack, needle);
// type of `s` is scope(string), owner is `haystack`
In other words, the type part of `scope!a(T)` is just `scope(T)`,
the owner is not part of the type and tracked separately.
Let's look at isInputRange:
template isInputRange(R)
{
enum bool isInputRange = is(typeof(
(inout int = 0)
{
R r = R.init; // can define a range object
if (r.empty) {} // can test for empty
r.popFront(); // can invoke popFront()
auto h = r.front; // can get the front of the
range
}));
}
auto byline = stdin.byLine();
`isInputRange!(typeof(byline))` expands to:
scope(ByLineImpl...) r = scope(ByLineImpl...).init;
if (r.empty) {}
r.popFront();
auto h = r.front;
The first line is valid, the last line too, because `scope` is
now part of the type and will of course be deduced by `auto`.
So this solves the template bloat problem, along with making
ElementType usable. That still leaves us with the problem of
forwarding and wrappers.
auto trace(alias func, Args...)(Args args) {
writeln("Calling " ~ func.stringof ~ "(" ~ args.stringof
~ ")");
return func(args);
}
auto s = trace!findSubstring(haystack, needle);
Here, `Args` becomes `(scope(string), scope(string))`. This
cannot work, of course, because it drops the owners. How can we
a) preserve the owners on the parameters, and b) propagate the
result's owner back outward?
@Manu: Under the condition that we'd find a solution for "perfect
forwarding", or imperfect if you like ;-), preferrably one also
applicable to `ref`, would you be okay with the above?