www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - auto & class members

reply =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
I use the D RX lib [1] and can create a filtered stream using the auto keyword:

struct a {
	SubjectObject!myType myStream;
	??? mySubStream;
}

void myfunc(){
	a myA = new a();

	auto mySubStream = a.myStream.filter!(a => a == myMessage);
	...
}

The problem is, that I don't find out what the type of mySubStream is, 
which I would like to make a member of the struct, so that I can 
reference it outside the function too.

How can I find out the type of an auto? This here seems to be a pretty 
complicated templated, nested type, whatever result.

[1] https://github.com/lempiji/rx

-- 
Robert M. Münch
http://www.saphirion.com
smarter | better | faster
May 20 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, May 20, 2018 16:30:10 Robert M. Münch via Digitalmars-d-learn 
wrote:
 I use the D RX lib [1] and can create a filtered stream using the auto
 keyword:

 struct a {
   SubjectObject!myType myStream;
   ??? mySubStream;
 }

 void myfunc(){
   a myA = new a();

   auto mySubStream = a.myStream.filter!(a => a == myMessage);
   ...
 }

 The problem is, that I don't find out what the type of mySubStream is,
 which I would like to make a member of the struct, so that I can
 reference it outside the function too.

 How can I find out the type of an auto? This here seems to be a pretty
 complicated templated, nested type, whatever result.

 [1] https://github.com/lempiji/rx
In cases like this, typeof is your friend. e.g. something like typeof(myStream.filter!(a => a == myMessage)) mySubStream; though you might have trouble with the lambda being subtly different type even if you replace myMessage in it with something that will work in the scope that mySubStream is being declared. However, that could be fixed by doing something like replacing the lambda with a free function or just creating a function that returns what you want to assign to mySubStream. Then you could just do something like typeof(myHelperFunc(myStream)) mySubStream; The exact solution can get a bit annoying in cases like this (it's arguably the biggest downside to auto returns), but typeof does provide a way out if you can get the expression to it to work. It is easier with local variables than member variables though, since you don't necessarily have everything you want to use in the expression that will give the variable its value available at the point that the variable is declared - but that's why a helper function can help, since it provides a way to encapsulate the expression and reuse it between the assignment and the declaration. - Jonathan M Davis
May 20 2018
parent reply =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
On 2018-05-20 14:49:59 +0000, Jonathan M Davis said:

 In cases like this, typeof is your friend. e.g. something like
 
 typeof(myStream.filter!(a => a == myMessage)) mySubStream;
Hi Jonathan, great! This got me a step further. So I can declare my member now. But I get an implict cast error when I try: class a { ... myStream; } class b { typeof(a.myStream.filter!(x => x == myMessage)) mySubStream; } void myFunc() { a myA = new a(); b myB = new b(); myB.mySubstream = myA.myStream.filter!(x => x == myMessage); } This gives (unnecessary stuff stripped): Error: cannot implicitly convert expression filter(...) of type app.myFunc.filter!(x => x == myMessage) to app.b.filter!(x => x == myMessage) Why is myFunc now entering the game? I mean it's just the function containing the code. It seems that: typeof(myA.myStream.filter!(x => x == myMessage)) and typeof(a.myStream.filter!(x => x == myMessage)) are not the same. But inside class b I can't use a specific variable instance. And in myFunc, I can't use a class type but need the specific instance. Any further idea?
 though you might have trouble with the lambda being subtly different type
 even if you replace myMessage in it with something that will work in the
 scope that mySubStream is being declared.
Not sure if the above problem is exactly what you mention here. This is all pretty tricky.
 However, that could be fixed by
 doing something like replacing the lambda with a free function or just
 creating a function that returns what you want to assign to mySubStream.
 Then you could just do something like
 
 typeof(myHelperFunc(myStream)) mySubStream;
 
 The exact solution can get a bit annoying in cases like this (it's arguably
 the biggest downside to auto returns), but typeof does provide a way out if
 you can get the expression to it to work. It is easier with local variables
 than member variables though, since you don't necessarily have everything
 you want to use in the expression that will give the variable its value
 available at the point that the variable is declared - but that's why
 a helper function can help, since it provides a way to encapsulate the
 expression and reuse it between the assignment and the declaration.
Not sure I understand every aspect but it's getting clearer... Thanks so far. -- Robert M. Münch http://www.saphirion.com smarter | better | faster
May 20 2018
parent reply =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
On 2018-05-20 17:40:39 +0000, Robert M. Münch said:

 Hi Jonathan, great! This got me a step further. So I can declare my 
 member now. But I get an implict cast error when I try:
 
 class a {
 	... myStream;
 }
 
 class b {
 	typeof(a.myStream.filter!(x => x == myMessage)) mySubStream;
 }
 
 void myFunc() {
 	a myA = new a();
 	b myB = new b();
 
 	myB.mySubstream = myA.myStream.filter!(x => x == myMessage);
 }
 
 This gives (unnecessary stuff stripped):
 
 Error: cannot implicitly convert expression filter(...) of type 
 app.myFunc.filter!(x => x == myMessage) to app.b.filter!(x => x == 
 myMessage)
Answering myself: Using an alias helps. alias typeof(a.myStream.filter!(x => x == myMessage)) myMessageType; class b { myMessageType mySubStream; } void myFunc() { a myA = new a(); b myB = new b(); myB.mySubstream = cast(myMessageType)myA.myStream.filter!(x => x == myMessage); } But I still don't understand why I can't write things explicitly but have to use an alias for this. -- Robert M. Münch http://www.saphirion.com smarter | better | faster
May 20 2018
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 05/20/2018 10:46 AM, Robert M. Münch wrote:

 But I still don't understand why I can't write things explicitly but
 have to use an alias for this.
Templatized range types work well when they are used as template arguments themselves. When you need to keep a single type like 'b' (i.e. b is not a template), and when you need to set a variable like mySubStream to a dynamic object, the solution is to use inputObject(): import std.algorithm; import std.range; class a { int[] myStream = [ 1, 2, 42, 100 ]; } int myMessage = 42; class b { InputRange!int mySubStream; } void myFunc() { a myA = new a(); b myB = new b(); myB.mySubStream = inputRangeObject(myA.myStream.filter!(x => x == myMessage)); assert(myB.mySubStream.equal([myMessage])); } void main() { myFunc(); } Now, mySubStream is a range variable that satisfies the input range interface and produces int elements. (Adjust accordingly.) You can use a more specialized range kind other than InputRange if the actual range supports it (e.g. ForwardRange!int, etc.): http://ddili.org/ders/d.en/ranges_more.html#ix_ranges_more.inputRangeObject https://dlang.org/phobos/std_range_interfaces.html#inputRangeObject Ali
May 21 2018
next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, May 21, 2018 11:13:16 Ali Çehreli via Digitalmars-d-learn wrote:
 On 05/20/2018 10:46 AM, Robert M. Münch wrote:
  > But I still don't understand why I can't write things explicitly but
  > have to use an alias for this.

 Templatized range types work well when they are used as template
 arguments themselves.

 When you need to keep a single type like 'b' (i.e. b is not a template),
 and when you need to set a variable like mySubStream to a dynamic
 object, the solution is to use inputObject():

 import std.algorithm;
 import std.range;

 class a {
      int[] myStream = [ 1, 2, 42, 100 ];
 }


 int myMessage = 42;

 class b {
      InputRange!int mySubStream;
 }

 void myFunc() {
      a myA = new a();
      b myB = new b();

      myB.mySubStream = inputRangeObject(myA.myStream.filter!(x => x ==
 myMessage));

      assert(myB.mySubStream.equal([myMessage]));
 }

 void main() {
      myFunc();
 }

 Now, mySubStream is a range variable that satisfies the input range
 interface and produces int elements. (Adjust accordingly.) You can use a
 more specialized range kind other than InputRange if the actual range
 supports it (e.g. ForwardRange!int, etc.):


 http://ddili.org/ders/d.en/ranges_more.html#ix_ranges_more.inputRangeObjec
 t

    https://dlang.org/phobos/std_range_interfaces.html#inputRangeObject
Wow. Someone actually uses those? I don't think that I've ever seen anyone try except when they didn't understand ranges properly and thought that all ranges derived from the interfaces in that module. I guess that they would work in this case, but I think that the normal solution is to use typeof (though as Robert here found, that can get a bit problematic when lambdas get involved, whereas your solution here is pretty straightforward). I'd be _very_ leery of using ForwardRange and the like though, since they're going to have to allocate on every call to save, which gets expensive, and far too often, range-based code doesn't call save correctly, meaning that you'll often hit bugs using a forward range that's a class. Phobos is a _lot_ better about it than it used to be, but I expect that there are still a few such lingering bugs in there, and I'd expect the average range-based code to screw it up. Really, the only way to get it right is to actually test your code with reference type ranges. If all you're using is a basic input range, then those interfaces just cost you the one allocation and should be fine, but beyond that, I wouldn't suggest using them if you can reasonably avoid it. And personally, I'd just use Steven's solution of using a wrapper function so that you can ensure that there's really only one lambda type involved, and typeof then works. - Jonathan M Davis
May 21 2018
prev sibling parent =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
On 2018-05-21 18:13:16 +0000, Ali ‡ehreli said:

 Templatized range types work well when they are used as template 
 arguments themselves.
 
 When you need to keep a single type like 'b' (i.e. b is not a 
 template), and when you need to set a variable like mySubStream to a 
 dynamic object, the solution is to use inputObject():
 ...
Thanks for the good example. The thing in my specific case is, that the streams are from a library, so no direct way to change their interface or so. But anyway, the more background about the whole topic, the better. -- Robert M. Münch http://www.saphirion.com smarter | better | faster
May 22 2018
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 5/20/18 1:46 PM, Robert M. Münch wrote:
 On 2018-05-20 17:40:39 +0000, Robert M. Münch said:
 
 Hi Jonathan, great! This got me a step further. So I can declare my 
 member now. But I get an implict cast error when I try:

 class a {
     ... myStream;
 }

 class b {
     typeof(a.myStream.filter!(x => x == myMessage)) mySubStream;
 }

 void myFunc() {
     a myA = new a();
     b myB = new b();

     myB.mySubstream = myA.myStream.filter!(x => x == myMessage);
 }

 This gives (unnecessary stuff stripped):

 Error: cannot implicitly convert expression filter(...) of type 
 app.myFunc.filter!(x => x == myMessage) to app.b.filter!(x => x == 
 myMessage)
Answering myself: Using an alias helps. alias typeof(a.myStream.filter!(x => x == myMessage)) myMessageType;
So the issue here is that the lambda function inside myFunc is DIFFERENT than the one inside b. They are both the same function, but with essentially different names. When you use the alias, both are using the same exact lambda. I see you are casting now as well, which looks horrible to me -- if you change something in your lambda now you are in for some trouble. What may make more sense (both for type sanity and for code reuse) is to wrap your call to filter into one place so it can be used wherever you need it: auto wrapStream(S)(S str) { return str.filter!(x => x == myMessage); } class b { typeof(wrapStream(a.init.myStream)()) mySubStream; } void myFunc() { a myA = new a; b myB = new b; myB.mySubstream = myA.myStream.wrapStream; } -Steve
May 21 2018
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, May 21, 2018 14:55:36 Steven Schveighoffer via Digitalmars-d-
learn wrote:
 On 5/20/18 1:46 PM, Robert M. Münch wrote:
 On 2018-05-20 17:40:39 +0000, Robert M. Münch said:
 Hi Jonathan, great! This got me a step further. So I can declare my
 member now. But I get an implict cast error when I try:

 class a {
     ... myStream;
 }

 class b {
     typeof(a.myStream.filter!(x => x == myMessage)) mySubStream;
 }

 void myFunc() {
     a myA = new a();
     b myB = new b();

     myB.mySubstream = myA.myStream.filter!(x => x == myMessage);
 }

 This gives (unnecessary stuff stripped):

 Error: cannot implicitly convert expression filter(...) of type
 app.myFunc.filter!(x => x == myMessage) to app.b.filter!(x => x ==
 myMessage)
Answering myself: Using an alias helps. alias typeof(a.myStream.filter!(x => x == myMessage)) myMessageType;
So the issue here is that the lambda function inside myFunc is DIFFERENT than the one inside b. They are both the same function, but with essentially different names. When you use the alias, both are using the same exact lambda. I see you are casting now as well, which looks horrible to me -- if you change something in your lambda now you are in for some trouble. What may make more sense (both for type sanity and for code reuse) is to wrap your call to filter into one place so it can be used wherever you need it: auto wrapStream(S)(S str) { return str.filter!(x => x == myMessage); } class b { typeof(wrapStream(a.init.myStream)()) mySubStream; } void myFunc() { a myA = new a; b myB = new b; myB.mySubstream = myA.myStream.wrapStream; }
That's basically what I was suggesting that he do, but I guess that I wasn't clear enough. - Jonathan M Davis
May 21 2018
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 5/21/18 3:22 PM, Jonathan M Davis wrote:
 
 That's basically what I was suggesting that he do, but I guess that I wasn't
 clear enough.
Well one thing that seems clear from this example -- we now have __traits(isSame) to tell if lambdas are the same, but it looks like the compiler doesn't subscribe to that belief... https://run.dlang.io/is/FW3mVq We should fix that... -Steve
May 21 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, May 21, 2018 16:05:00 Steven Schveighoffer via Digitalmars-d-
learn wrote:
 On 5/21/18 3:22 PM, Jonathan M Davis wrote:
 That's basically what I was suggesting that he do, but I guess that I
 wasn't clear enough.
Well one thing that seems clear from this example -- we now have __traits(isSame) to tell if lambdas are the same, but it looks like the compiler doesn't subscribe to that belief... https://run.dlang.io/is/FW3mVq We should fix that...
Yeah. That part of lambdas has always been a problem. My guess here is that the problem stems from the fact that they're declared in separate scopes, but I don't know. Regardless of the reason for the failure though, that example really needs to work, or most anything that cares about lambdas being the same is going to have problems. - Jonathan M Davis
May 21 2018
parent reply =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
On 2018-05-21 20:17:04 +0000, Jonathan M Davis said:

 On Monday, May 21, 2018 16:05:00 Steven Schveighoffer via Digitalmars-d-
 learn wrote:
 
 Well one thing that seems clear from this example -- we now have
 __traits(isSame) to tell if lambdas are the same, but it looks like the
 compiler doesn't subscribe to that belief...
 
 https://run.dlang.io/is/FW3mVq
 
 We should fix that...
Yeah. That part of lambdas has always been a problem. My guess here is that the problem stems from the fact that they're declared in separate scopes, but I don't know. Regardless of the reason for the failure though, that example really needs to work, or most anything that cares about lambdas being the same is going to have problems.
I think that's exactly the problem: I assumed that it's about the lambdas and associated types but would have never guessed that names, scope etc. play a role as well. Is this somewhere documented? Or at least a hint, would help a lot to be aware of this pitfall. -- Robert M. Münch http://www.saphirion.com smarter | better | faster
May 22 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, May 22, 2018 10:43:38 Robert M. Münch via Digitalmars-d-learn 
wrote:
 On 2018-05-21 20:17:04 +0000, Jonathan M Davis said:
 On Monday, May 21, 2018 16:05:00 Steven Schveighoffer via Digitalmars-d-

 learn wrote:
 Well one thing that seems clear from this example -- we now have
 __traits(isSame) to tell if lambdas are the same, but it looks like the
 compiler doesn't subscribe to that belief...

 https://run.dlang.io/is/FW3mVq

 We should fix that...
Yeah. That part of lambdas has always been a problem. My guess here is that the problem stems from the fact that they're declared in separate scopes, but I don't know. Regardless of the reason for the failure though, that example really needs to work, or most anything that cares about lambdas being the same is going to have problems.
I think that's exactly the problem: I assumed that it's about the lambdas and associated types but would have never guessed that names, scope etc. play a role as well. Is this somewhere documented? Or at least a hint, would help a lot to be aware of this pitfall.
The issue is that you actually have different lambdas. They _look_ the same, but they aren't actually the same function. The compiler is not good about recognizing that two lambdas are identical. When Steven said that they had different names, he meant that it was like if you had declared: int foo(int i) { return i + 42; } int bar(int i) { return i + 42; } Both functions are identical, but the compiler doesn't see that. It just looks at the signatures - and while everything about them is functionally equivalent, they have different names. Basically, the compiler is too dumb to figure out when two lambdas are actually identical. Some work has been done towards making it that smart, but there is still clearly more work to be done. As for how well any of this is documented, I don't know, but I suspect that at most, the spec has a line or two about it somewhere, especially since it's really not something that was planned for per se. It's just a natural fallout of how lambdas work and is surprisingly difficult to fix. - Jonathan M Davis
May 22 2018
prev sibling parent reply =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
On 2018-05-21 18:55:36 +0000, Steven Schveighoffer said:

 So the issue here is that the lambda function inside myFunc is 
 DIFFERENT than the one inside b. They are both the same function, but 
 with essentially different names.
Aha... that explains it pretty good.
 When you use the alias, both are using the same exact lambda.
Ok. I didn't expect that the name is relevant in this case, instead assumed that only the types need to match.
 I see you are casting now as well,
Do I? Not that I'm aware of it in my pseudo-code example...
 What may make more sense (both for type sanity and for code reuse) is 
 to wrap your call to filter into one place so it can be used wherever 
 you need it:
 
 auto wrapStream(S)(S str) { return str.filter!(x => x == myMessage); }
 
 class b
 {
     typeof(wrapStream(a.init.myStream)()) mySubStream;
 }
 
 void myFunc() {
     a myA = new a;
     b myB = new b;
     myB.mySubstream = myA.myStream.wrapStream;
 }
This would require one wrap function per different lambda, right? Assume I have 50-100 of these. Maybe the myMessage value can be given as parameter and with this becomes more like a "filter factory". Not sure if this would work. -- Robert M. Münch http://www.saphirion.com smarter | better | faster
May 22 2018
next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, May 22, 2018 10:40:55 Robert M. Münch via Digitalmars-d-learn 
wrote:
 This would require one wrap function per different lambda, right?
 Assume I have 50-100 of these. Maybe the myMessage value can be given
 as parameter and with this becomes more like a "filter factory". Not
 sure if this would work.
Pretty much the only time that this sort of thing pops up is when you have to declare a variable that's a range (or some other similarly generic type) separately from where it's initialized. I'd expect that your application would have to be very large to have 50 - 100 instances of that. If you really were hitting it a lot, then maybe it would make sense to try and figure out a way to avoid having to declare a wrapper function, but in my experience, this sort of thing simply doesn't come up all that often. It's definitely an issue, and occasionally someone will come here and ask how to deal with it, but I'd be worried if it came up often enough that creating a wrapper function to deal with it was a problem. The other way to fix the problem is to just call std.array.array on the range to get a dynamic array. It does mean allocating, but you run into fewer problems related to type inference, since you can then easily type the type rather than having to use type inference to get it. - Jonathan M Davis
May 22 2018
prev sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 5/22/18 4:40 AM, Robert M. Münch wrote:
 On 2018-05-21 18:55:36 +0000, Steven Schveighoffer said:
 
 When you use the alias, both are using the same exact lambda.
Ok. I didn't expect that the name is relevant in this case, instead assumed that only the types need to match.
The type is the problem. The type returned by filter is parameterized on that *specific* lambda. If you look at the error message, it says something like "lamda1" and "lambda4" in the type for filter. In order to make this work, the compiler would have to make the name of the lambda based on the actual AST inside it. I think something like that should be done.
 
 I see you are casting now as well,
Do I? Not that I'm aware of it in my pseudo-code example...
Haha, looking back, I see you didn't cast originally, which is probably the reason it didn't work :) Here is the line from your revised example after Jonathan showed you how to declare a member of that type: myB.mySubstream = myA.myStream.filter!(x => x == myMessage); And here is the subsequent line: myB.mySubstream = cast(myMessageType)myA.myStream.filter!(x => x == myMessage); Both exactly the same, but one forces the cast. Your first line could have been done: myB.mySubstream = cast(typeof(myB.mySubstream))myA.myStream.filter!(x => x == myMessage); Giving a name helps to make the code less verbose, but essentially that is what you are doing -- forcing the cast.
 What may make more sense (both for type sanity and for code reuse) is 
 to wrap your call to filter into one place so it can be used wherever 
 you need it:

 auto wrapStream(S)(S str) { return str.filter!(x => x == myMessage); }

 class b
 {
     typeof(wrapStream(a.init.myStream)()) mySubStream;
 }

 void myFunc() {
     a myA = new a;
     b myB = new b;
     myB.mySubstream = myA.myStream.wrapStream;
 }
This would require one wrap function per different lambda, right? Assume I have 50-100 of these. Maybe the myMessage value can be given as parameter and with this becomes more like a "filter factory". Not sure if this would work
Well, you then have to have 50-100 types of b with the correct member. Unless... you want to parameterize b, in which case it becomes REALLY easy: class b(FilterType) { FilterType mySubstream; } auto makeB(FilterType)(FilterType f) { return new b!FilterType(f); } ... auto myB = myA.myStream.filter!(x => coolCrazyFunction(x)).makeB; -Steve
May 22 2018