www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why does choose not work here

reply Matt <gamblemj gmail.com> writes:
I'm having some trouble with a "Program exited with code 
-1073741819" error in some code I'm writing and I would 
appreciate any help/insight.

The problem stems from some incompatibility between the Phobos 
function "choose" and the template function "myFilter" which 
returns a range. The code below is a simplified version of what 
I'm trying to do. myFilter is a dummy/stand-in function that 
highlights the error in my program (i.e. My function is not 
simply a filter; it is much more complicated but unnecessary to 
highlight my problem). In PairedA, sometimes previous will be 
null. I need the uniqIntervals member function to do the right 
thing depending on if previous is null or not. In an ideal 
situation uniqIntervals would return a forward range where the 
work would be done in a lazy fashion.

Version 1 works but is not lazy and does not use choose.
Version 2 works but requires converting the ranges to 
ForwardRange classes, (I'd prefer to not use an unnecessary level 
of indirection).
Version 3 uses the Phobos function filter which works inside 
choose (but there is no Phobos function that can actual do why 
myFilter stands in for. It's just interesting that the Phobos 
function works where myFilter doesn't as they are both template 
functions.
Version 4 does not work when PairedA.previous is null. I'd love 
to understand why.

I'd also love to some one to show me what the best way to do this 
would be. If version 2 is the best I can do, I'll live with it.

Thanks so much, the code is below:

auto myFilter(R1, R2)(R1 a, R2 b)
{
	import std.algorithm : filter, canFind;
	return a.filter!(c => b.canFind(c));
}

struct A
{
	uint[] starts, stops;

	import std.range : ForwardRange, inputRangeObject;
	import std.typecons : Tuple;
	ForwardRange!(Tuple!(uint,uint)) intervalRange()  property
	{
		import std.algorithm : map;
		import std.range : zip;
		import std.typecons : tuple;
		return zip(starts,stops).map!(a => 
tuple(a[0],a[1])).inputRangeObject;
	}
}
	
struct PairedA
{
	//version 1
	// auto uniqIntervals()  property
	// {
	// 	import std.array : array;
	// 	if (previous is null) return primary.intervalRange.array;
	// 	return primary.intervalRange
	// 		.myFilter(previous.intervalRange).array;
	// }

	//version 2
	// import std.range : ForwardRange, inputRangeObject;
	// import std.typecons : Tuple;
	// ForwardRange!(Tuple!(uint,uint)) uniqIntervals()  property
	// {
	// 	if (previous is null) return 
primary.intervalRange.inputRangeObject;
	// 	return primary.intervalRange
	// 		.myFilter(previous.intervalRange).inputRangeObject;
	// }

	//version 3
	// auto uniqIntervals()  property
	// {
	// 	import std.range : choose;
	// 	import std.algorithm : filter, canFind;
	// 	return choose(previous is null,
	// 		primary.intervalRange,
	// 		primary.intervalRange
	// 			.filter!(a => previous.intervalRange.canFind(a)));
	// }

	//version 4
	auto uniqIntervals()  property
	{
		import std.range : choose;
		import std.algorithm : filter, canFind;
		return choose(previous is null,
			primary.intervalRange,
			primary.intervalRange
				.myFilter(previous.intervalRange));
	}

	A primary;
	A* previous;
}

unittest
{
	uint[] startsA = [1,100,1000,10000];
	uint[] stopsA = [2,200,2000,20000];
	uint[] startsB = [1,100];
	uint[] stopsB = [2,200];
	
	auto a1 = A(startsA, stopsA);
	auto a2 = A(startsB, stopsB);
	
	auto p = PairedA(a1, &a2);
	auto p2 = PairedA(a1, null);
	
	import std.stdio : writeln;
	writeln(p.uniqIntervals);//always works
	writeln(p2.uniqIntervals);//Program exited with code -1073741819 
for version 4
}
Aug 01 2019
parent reply ag0aep6g <anonymous example.com> writes:
On 01.08.19 22:23, Matt wrote:
 Version 4 does not work when PairedA.previous is null. I'd love to 
 understand why.
 
[...]
 
 auto myFilter(R1, R2)(R1 a, R2 b)
 {
      import std.algorithm : filter, canFind;
      return a.filter!(c => b.canFind(c));
 }
 
 struct A
 {
      uint[] starts, stops;
 
      import std.range : ForwardRange, inputRangeObject;
      import std.typecons : Tuple;
      ForwardRange!(Tuple!(uint,uint)) intervalRange()  property
      {
          import std.algorithm : map;
          import std.range : zip;
          import std.typecons : tuple;
          return zip(starts,stops).map!(a => 
 tuple(a[0],a[1])).inputRangeObject;
      }
 }
 
 struct PairedA
 {
[...]
      //version 4
      auto uniqIntervals()  property
      {
          import std.range : choose;
          import std.algorithm : filter, canFind;
          return choose(previous is null,
              primary.intervalRange,
              primary.intervalRange
                  .myFilter(previous.intervalRange));
      }
 
      A primary;
      A* previous;
 }
`choose`'s parameters aren't lazy. So the second argument is evaluated even when `previous is null`. That means `intervalRange` is called on a null `previous`. And that fails, of course, because `intervalRange` can't access `starts` or `stops` when `this` is null.
Aug 01 2019
parent reply Matt <gamblemj gmail.com> writes:
On Thursday, 1 August 2019 at 21:12:51 UTC, ag0aep6g wrote:
 `choose`'s parameters aren't lazy. So the second argument is 
 evaluated even when `previous is null`. That means 
 `intervalRange` is called on a null `previous`. And that fails, 
 of course, because `intervalRange` can't access `starts` or 
 `stops` when `this` is null.
I forgot about lazy parameters. I tried changes myFilters second parameter to lazy, but that didn't help. So I guess I'm stuck with the "if" version using the ForwardRange interface (version 2)? Anyone have any other thoughts?
Aug 01 2019
parent berni <someone somewhere.com> writes:
On Thursday, 1 August 2019 at 21:26:10 UTC, Matt wrote:
 Anyone have any other thoughts?
I tried to simplify your example a little bit: import std.stdio; import std.range; import std.algorithm; auto myFilter(R1, R2)(R1 a, R2 b) { return a.filter!(c => c==b.front); } struct A { int[] starts; auto intervalRange() property { return starts; } } auto uniqIntervalsA(A primary, A* previous) property { return choose(previous is null, primary.intervalRange, primary.intervalRange.filter!(a => a==previous.intervalRange.front)); } auto uniqIntervalsB(A primary, A* previous) property { return choose(previous is null, primary.intervalRange, primary.intervalRange.myFilter(previous.intervalRange)); } unittest { auto a1 = A([1]); writeln(uniqIntervalsA(a1,&a1)); writeln(uniqIntervalsA(a1,null)); writeln(uniqIntervalsB(a1,&a1)); writeln(uniqIntervalsB(a1,null)); } The strange thing is, that even if you replace "return a.filter!(c => c==b.front);" by "return a.filter!(c => true);" the problem remains (but now you can use lazy). As I'm not an expert myself, I'm not sure how to analyze this. But I think, both parameters to "choose" are evaluated (that is they are not lazy), but "myFilter" uses it's parameter immediately to give "b" a value, while "filter" just hands in an anonymous function without using it ever. Hope, this helps to get further down the way to a solution...
Aug 02 2019