www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Has the ban on returning function nested structs been lifted?

reply Andrej Mitrovic <none none.none> writes:
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
next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
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
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
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
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
parent reply bearophile <bearophileHUGS lycos.com> writes:
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
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
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
prev sibling parent spir <denis.spir gmail.com> writes:
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
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling next sibling parent so <so so.so> writes:
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
prev sibling next sibling parent Tomek =?ISO-8859-2?B?U293afFza2k=?= <just ask.me> writes:
Andrei Alexandrescu napisa=B3:

 Auto returns + local types =3D just awesome.

Why is it awesome? --=20 Tomek
Mar 18 2011
prev sibling next sibling parent so <so so.so> writes:
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
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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