digitalmars.D - Has the ban on returning function nested structs been lifted?
- Andrej Mitrovic <none none.none> Mar 18 2011
- Andrej Mitrovic <andrej.mitrovich gmail.com> Mar 18 2011
- spir <denis.spir gmail.com> Mar 18 2011
- Jonathan M Davis <jmdavisProg gmx.com> Mar 18 2011
- Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> Mar 18 2011
- bearophile <bearophileHUGS lycos.com> Mar 18 2011
- Jonathan M Davis <jmdavisProg gmx.com> Mar 19 2011
- "Simen kjaeraas" <simen.kjaras gmail.com> Mar 19 2011
- spir <denis.spir gmail.com> Mar 19 2011
- spir <denis.spir gmail.com> Mar 19 2011
- Jonathan M Davis <jmdavisProg gmx.com> Mar 18 2011
- Andrej Mitrovic <andrej.mitrovich gmail.com> Mar 18 2011
- so <so so.so> Mar 18 2011
- Tomek =?ISO-8859-2?B?U293afFza2k=?= <just ask.me> Mar 18 2011
- so <so so.so> Mar 18 2011
- Jonathan M Davis <jmdavisProg gmx.com> Mar 18 2011
- "Simen kjaeraas" <simen.kjaras gmail.com> Mar 19 2011
- "Simen kjaeraas" <simen.kjaras gmail.com> Mar 19 2011
From TDPL, page 263:
"Nested struct objects cannot be returned from functions because the caller
doesn't have access to their type".
However auto seems to work around this limitation:
module structInFunction;
import std.stdio;
void main()
{
auto local = foo(0);
assert(local.sum() == 30);
writeln(typeid(local)); // structInFunction.foo.Local
}
auto foo(int a)
{
int z = a + 10;
struct Local
{
int x;
int y;
int sum()
{
return x + y + z;
}
}
return Local(10, 10);
}
I don't have a use case for this, personally. But it does seem to work.
Well, almost. The following issues a runtime error:
module structInFunction;
import std.stdio;
void main()
{
auto local = foo(0);
writeln(local.sum());
assert(local.sum() == 30);
writeln(typeid(local)); // structInFunction.foo.Local
}
auto foo(int a)
{
int z = a + 10;
struct Local
{
int x = 10;
int y = 10;
int sum()
{
return x + y + z;
}
}
Local local;
return local;
}
object.Error: Access Violation
An explicit call to the ctor like this works with no runtime errors:
auto local = Local();
return local;
Mar 18 2011
This seems to work with classes as well. The TDPL has an example of a
class that subclasses a class definition in module scope. But this one
is defined in function scope, doesn't derive, and still works:
void main()
{
auto local = foo(0);
assert(local.sum() == 30);
}
auto foo(int a)
{
int z = a + 10;
class Local
{
int x = 10;
int y = 10;
int sum()
{
return x + y + z;
}
}
return new Local();
}
Mar 18 2011
On 03/18/2011 06:05 PM, Andrej Mitrovic wrote:From TDPL, page 263: "Nested struct objects cannot be returned from functions because the caller doesn't have access to their type". However auto seems to work around this limitation: module structInFunction; import std.stdio; void main() { auto local = foo(0); assert(local.sum() == 30); writeln(typeid(local)); // structInFunction.foo.Local } auto foo(int a) { int z = a + 10; struct Local { int x; int y; int sum() { return x + y + z; } } return Local(10, 10); } I don't have a use case for this, personally. But it does seem to work. Well, almost. The following issues a runtime error: module structInFunction; import std.stdio; void main() { auto local = foo(0); writeln(local.sum()); assert(local.sum() == 30); writeln(typeid(local)); // structInFunction.foo.Local } auto foo(int a) { int z = a + 10; struct Local { int x = 10; int y = 10; int sum() { return x + y + z; } } Local local; return local; } object.Error: Access Violation An explicit call to the ctor like this works with no runtime errors: auto local = Local(); return local;
Great magic auto is. Auto with you be. Denis -- _________________ vita es estrany spir.wikidot.com
Mar 18 2011
On Friday, March 18, 2011 10:19:10 spir wrote:On 03/18/2011 06:05 PM, Andrej Mitrovic wrote:From TDPL, page 263: "Nested struct objects cannot be returned from functions because the caller doesn't have access to their type". However auto seems to work around this limitation: module structInFunction; import std.stdio; void main() { auto local = foo(0); assert(local.sum() == 30); writeln(typeid(local)); // structInFunction.foo.Local } auto foo(int a) { int z = a + 10; struct Local { int x; int y; int sum() { return x + y + z; } } return Local(10, 10); } I don't have a use case for this, personally. But it does seem to work. Well, almost. The following issues a runtime error: module structInFunction; import std.stdio; void main() { auto local = foo(0); writeln(local.sum()); assert(local.sum() == 30); writeln(typeid(local)); // structInFunction.foo.Local } auto foo(int a) { int z = a + 10; struct Local { int x = 10; int y = 10; int sum() { return x + y + z; } } Local local; return local; } object.Error: Access Violation An explicit call to the ctor like this works with no runtime errors: auto local = Local(); return local;
Great magic auto is. Auto with you be.
Yeah. Actually, Andrei has been making changes to std.range and std.algorithm so that _most_ functions which return new range types work this way. So, if TDPL says that it's illegal, it probably needs to be changed. Either that or we need to stop switching over to doing things that way. On the whole though, it strikes me as a positive change. - Jonathan M Davis
Mar 18 2011
On 3/18/11 1:21 PM, Jonathan M Davis wrote:Yeah. Actually, Andrei has been making changes to std.range and std.algorithm so that _most_ functions which return new range types work this way. So, if TDPL says that it's illegal, it probably needs to be changed. Either that or we need to stop switching over to doing things that way. On the whole though, it strikes me as a positive change. - Jonathan M Davis
Yah, TDPL needs changing. Auto returns + local types = just awesome. Andrei
Mar 18 2011
Jonathan M Davis:Actually, the coolest part about it IMHO is that it highlights the fact that you should be using auto with std.algorithm and _not_ care about the exact types of the return types. Knowing the exact return type for those functions is generally unnecessary and is often scary anyway (especially with the functions which return lazy ranges like map and until). Making the functions return auto and completely hiding the return type pretty much forces the issue. There's still likely to be some confusion for those new to D, but it makes the proper way to use std.algorithm more obvious. I'd hate to deal with any code which used std.algorithm without auto. That would get ugly _fast_.
auto variable inference is indeed almost necessary if you want to use lazy functions as the ones in Phobos. But I have to say that those types are scary because of the current design of those Phobos higher order functions. In Haskell if you have an iterable and you perform a map on it using a function that returns an int, you produce something like a [Int], that's a lazy list of machine integers. This is a very simple type. If you perform another map on that list, and the mapping function returns an int again, the type of the whole result is [Int] still. The type you work with doesn't grow more and more as with Phobos functions. Designers of C# LINQ have found a more complex solution, they build a tree of lazy delegates... Bye, bearophile
Mar 18 2011
On Saturday 19 March 2011 02:27:46 Simen kjaeraas wrote:On Fri, 18 Mar 2011 23:48:53 +0100, bearophile <bearophileHUGS lycos.com> wrote:Jonathan M Davis:Actually, the coolest part about it IMHO is that it highlights the fact that you should be using auto with std.algorithm and _not_ care about the exact types of the return types. Knowing the exact return type for those functions is generally unnecessary and is often scary anyway (especially with the functions which return lazy ranges like map and until). Making the functions return auto and completely hiding the return type pretty much forces the issue. There's still likely to be some confusion for those new to D, but it makes the proper way to use std.algorithm more obvious. I'd hate to deal with any code which used std.algorithm without auto. That would get ugly _fast_.
auto variable inference is indeed almost necessary if you want to use lazy functions as the ones in Phobos. But I have to say that those types are scary because of the current design of those Phobos higher order functions. In Haskell if you have an iterable and you perform a map on it using a function that returns an int, you produce something like a [Int], that's a lazy list of machine integers. This is a very simple type. If you perform another map on that list, and the mapping function returns an int again, the type of the whole result is [Int] still. The type you work with doesn't grow more and more as with Phobos functions. Designers of C# LINQ have found a more complex solution, they build a tree of lazy delegates...
And we can have something similar in D: struct Range( T ) { void delegate( ) popFrontDg; bool delegate( ) emptyDg; T delegate( ) frontDg; this( R )( R range ) if ( isForwardRange!R && is( ElementType!R == T ) ) { auto rng = range.save(); popFrontDg = ( ){ rng.popFront(); }; emptyDg = ( ){ return rng.empty; }; frontDg = ( ){ return rng.front; }; } property T front( ) { return frontDg( ); } property bool empty( ) { return emptyDg( ); } void popFront( ) { popFrontDg( ); } } Range!(ElementType!R) range( R )( R rng ) if ( isForwardRange!R ) { return Range!(ElementType!R)( rng ); } There are times when I've wanted something like this because I don't know the resultant type of a bunch of range operations, but have to save it in a struct or class.
typeof is your friend. - Jonathan M Davis
Mar 19 2011
On Sat, 19 Mar 2011 11:18:17 +0100, Jonathan M Davis <jmdavisProg gmx.com> wrote:There are times when I've wanted something like this because I don't know the resultant type of a bunch of range operations, but have to save it in a struct or class.
typeof is your friend.
Only when there is a definite type. Consider: struct foo { Range!int rng; this( int[] arr, bool b ) { if ( b ) { rng = arr; } else { rng = map!"a+b"( arr ); } } } -- Simen
Mar 19 2011
On 03/19/2011 10:27 AM, Simen kjaeraas wrote:On Fri, 18 Mar 2011 23:48:53 +0100, bearophile <bearophileHUGS lycos.com> wrote:Jonathan M Davis:Actually, the coolest part about it IMHO is that it highlights the fact that you should be using auto with std.algorithm and _not_ care about the exact types of the return types. Knowing the exact return type for those functions is generally unnecessary and is often scary anyway (especially with the functions which return lazy ranges like map and until). Making the functions return auto and completely hiding the return type pretty much forces the issue. There's still likely to be some confusion for those new to D, but it makes the proper way to use std.algorithm more obvious. I'd hate to deal with any code which used std.algorithm without auto. That would get ugly _fast_.
auto variable inference is indeed almost necessary if you want to use lazy functions as the ones in Phobos. But I have to say that those types are scary because of the current design of those Phobos higher order functions. In Haskell if you have an iterable and you perform a map on it using a function that returns an int, you produce something like a [Int], that's a lazy list of machine integers. This is a very simple type. If you perform another map on that list, and the mapping function returns an int again, the type of the whole result is [Int] still. The type you work with doesn't grow more and more as with Phobos functions. Designers of C# LINQ have found a more complex solution, they build a tree of lazy delegates...
And we can have something similar in D: struct Range( T ) { void delegate( ) popFrontDg; bool delegate( ) emptyDg; T delegate( ) frontDg; this( R )( R range ) if ( isForwardRange!R && is( ElementType!R == T ) ) { auto rng = range.save(); popFrontDg = ( ){ rng.popFront(); }; emptyDg = ( ){ return rng.empty; }; frontDg = ( ){ return rng.front; }; } property T front( ) { return frontDg( ); } property bool empty( ) { return emptyDg( ); } void popFront( ) { popFrontDg( ); } } Range!(ElementType!R) range( R )( R rng ) if ( isForwardRange!R ) { return Range!(ElementType!R)( rng ); } There are times when I've wanted something like this because I don't know the resultant type of a bunch of range operations, but have to save it in a struct or class.
I guess something similar should be the base design of ranges. "Range of X" could simply mean "lazy sequence of X", an on-demand array (lol); and that would be the return type of every function returning a range. The complexity (of filter-ing, map-ping, find-ind) could be hidden inside the object, not exposed in the outer type. Denis -- _________________ vita es estrany spir.wikidot.com
Mar 19 2011
On 03/19/2011 01:40 PM, Simen kjaeraas wrote:On Sat, 19 Mar 2011 13:05:59 +0100, spir <denis.spir gmail.com> wrote:I guess something similar should be the base design of ranges. "Range of X" could simply mean "lazy sequence of X", an on-demand array (lol); and that would be the return type of every function returning a range. The complexity (of filter-ing, map-ping, find-ind) could be hidden inside the object, not exposed in the outer type.
Such a scheme precludes the usage of structs as ranges, though. It would require virtual functions.
Oh, yes, seems you're right. Too bad. Denis -- _________________ vita es estrany spir.wikidot.com
Mar 19 2011
On Friday 18 March 2011 12:48:17 Andrei Alexandrescu wrote:On 3/18/11 1:21 PM, Jonathan M Davis wrote:Yeah. Actually, Andrei has been making changes to std.range and std.algorithm so that _most_ functions which return new range types work this way. So, if TDPL says that it's illegal, it probably needs to be changed. Either that or we need to stop switching over to doing things that way. On the whole though, it strikes me as a positive change. - Jonathan M Davis
Yah, TDPL needs changing. Auto returns + local types = just awesome.
Actually, the coolest part about it IMHO is that it highlights the fact that you should be using auto with std.algorithm and _not_ care about the exact types of the return types. Knowing the exact return type for those functions is generally unnecessary and is often scary anyway (especially with the functions which return lazy ranges like map and until). Making the functions return auto and completely hiding the return type pretty much forces the issue. There's still likely to be some confusion for those new to D, but it makes the proper way to use std.algorithm more obvious. I'd hate to deal with any code which used std.algorithm without auto. That would get ugly _fast_. So, yeah. auto returns + local types are indeed awesome. - Jonathan M Davis
Mar 18 2011
Can auto functions with local types work if you're distributing your library in binary form (.lib) and only .di interface files?
Mar 18 2011
On Fri, 18 Mar 2011 22:25:49 +0200, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:Can auto functions with local types work if you're distributing your library in binary form (.lib) and only .di interface files?
As much as i love to have it (IMO a big issue for library design in general) i am afraid it is not possible because of dynamic libraries. If only we could expose only the parts we want, with zero overhead (I don't know, maybe it is already possible somehow). There is not a single elegant solution in the languages i know. --- di file struct A { // imposter A { method1 method2 } --- d file struct A { method1 method2 method3 ... // data int a, b, c; }
Mar 18 2011
Andrei Alexandrescu napisa=B3:Auto returns + local types =3D just awesome.
Why is it awesome? --=20 Tomek
Mar 18 2011
On Sat, 19 Mar 2011 00:07:40 +0200, Tomek Sowi=C5=84ski <just ask.me> wr= ote:Andrei Alexandrescu napisa=C5=82:Auto returns + local types =3D just awesome.
Why is it awesome?
Clean namespace and clean implementation are the first two i can think o= f.
Mar 18 2011
On Friday, March 18, 2011 15:48:53 bearophile wrote:Jonathan M Davis:Actually, the coolest part about it IMHO is that it highlights the fact that you should be using auto with std.algorithm and _not_ care about the exact types of the return types. Knowing the exact return type for those functions is generally unnecessary and is often scary anyway (especially with the functions which return lazy ranges like map and until). Making the functions return auto and completely hiding the return type pretty much forces the issue. There's still likely to be some confusion for those new to D, but it makes the proper way to use std.algorithm more obvious. I'd hate to deal with any code which used std.algorithm without auto. That would get ugly _fast_.
auto variable inference is indeed almost necessary if you want to use lazy functions as the ones in Phobos. But I have to say that those types are scary because of the current design of those Phobos higher order functions. In Haskell if you have an iterable and you perform a map on it using a function that returns an int, you produce something like a [Int], that's a lazy list of machine integers. This is a very simple type. If you perform another map on that list, and the mapping function returns an int again, the type of the whole result is [Int] still. The type you work with doesn't grow more and more as with Phobos functions. Designers of C# LINQ have found a more complex solution, they build a tree of lazy delegates...
You get the simple types in Haskell, because _everything_ in Haskell is lazy. _Nothing_ is actually computed until it has to be. So, the fact that a list is lazily executed doesn't really affect the type system. Not everything is lazy in D, so that doesn't work. And honestly, while the return type of functions like map and until may look fairly ugly, auto makes their ugliness pretty much irrelevant. I think that D has a solid solution. Haskell looks cleaner only because it forces laziness on everything. - Jonathan M Davis
Mar 18 2011
On Fri, 18 Mar 2011 23:48:53 +0100, bearophile <bearophileHUGS lycos.com> wrote:Jonathan M Davis:Actually, the coolest part about it IMHO is that it highlights the fact that you should be using auto with std.algorithm and _not_ care about the exact types of the return types. Knowing the exact return type for those functions is generally unnecessary and is often scary anyway (especially with the functions which return lazy ranges like map and until). Making the functions return auto and completely hiding the return type pretty much forces the issue. There's still likely to be some confusion for those new to D, but it makes the proper way to use std.algorithm more obvious. I'd hate to deal with any code which used std.algorithm without auto. That would get ugly _fast_.
auto variable inference is indeed almost necessary if you want to use lazy functions as the ones in Phobos. But I have to say that those types are scary because of the current design of those Phobos higher order functions. In Haskell if you have an iterable and you perform a map on it using a function that returns an int, you produce something like a [Int], that's a lazy list of machine integers. This is a very simple type. If you perform another map on that list, and the mapping function returns an int again, the type of the whole result is [Int] still. The type you work with doesn't grow more and more as with Phobos functions. Designers of C# LINQ have found a more complex solution, they build a tree of lazy delegates...
And we can have something similar in D: struct Range( T ) { void delegate( ) popFrontDg; bool delegate( ) emptyDg; T delegate( ) frontDg; this( R )( R range ) if ( isForwardRange!R && is( ElementType!R == T ) ) { auto rng = range.save(); popFrontDg = ( ){ rng.popFront(); }; emptyDg = ( ){ return rng.empty; }; frontDg = ( ){ return rng.front; }; } property T front( ) { return frontDg( ); } property bool empty( ) { return emptyDg( ); } void popFront( ) { popFrontDg( ); } } Range!(ElementType!R) range( R )( R rng ) if ( isForwardRange!R ) { return Range!(ElementType!R)( rng ); } There are times when I've wanted something like this because I don't know the resultant type of a bunch of range operations, but have to save it in a struct or class. -- Simen
Mar 19 2011
On Sat, 19 Mar 2011 13:05:59 +0100, spir <denis.spir gmail.com> wrote:I guess something similar should be the base design of ranges. "Range of X" could simply mean "lazy sequence of X", an on-demand array (lol); and that would be the return type of every function returning a range. The complexity (of filter-ing, map-ping, find-ind) could be hidden inside the object, not exposed in the outer type.
Such a scheme precludes the usage of structs as ranges, though. It would require virtual functions. -- Simen
Mar 19 2011









Andrej Mitrovic <andrej.mitrovich gmail.com> 