Thread overview
Why does choose not work here
Aug 01, 2019
Matt
Aug 01, 2019
ag0aep6g
Aug 01, 2019
Matt
Aug 02, 2019
berni
August 01, 2019
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
}
August 01, 2019
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.
August 01, 2019
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?
August 02, 2019
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...