www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - how to store a range transformed by a range function?

reply "Vlad Levenfeld" <vlevenfeld gmail.com> writes:
I've got an app that uses a custom range type over some 
preallocated arrays. These arrays are accessed through a getter 
fuction (supplied by the struct that is maintaining a unique 
reference to the array) that returns a T[].

Typical usage involves a source range, target range, and a graph 
of maps, filters, zip and reductions piping data from the source 
to the target. It is the fastest range-based processing scheme I 
could think of (I say because it avoids the GC as far as I know).

I would like if I had the option to not care exactly where the 
actual data is coming from (or going to) during computation. I'd 
like to be able to take a node in the computational graph and 
store it so that when I access it later, it pulls data from some 
managed array (or perhaps a transformed range from another saved 
node) and returns the transformed range.

For example, lets say I had a couple of managed arrays of 
doubles, and I had some computational path that, at some point, 
took these two ranges and zipped them before passing them along. 
At that node (where the zip is performed) I'd like to save a 
struct that, upon request, returns a Tuple!(double, double) range 
populated with the current values of the source double[]s. Then 
anything which uses that struct is only exposed to a range of 
ordered pairs, which fell from the sky as far as anything on the 
client-side of the interface is concerned.

Of course I can't, not right off the bat, because storage 
requires specifying a type to store, and the actual type of a 
lazy-evaluated functional result depends on how it was 
constructed ("Voldemort types" as I've heard them called), so 
there's no way to, say, keep an array of these (unless they all 
have the same source types and underwent the same 
transformations). So even if I could create such a "midway node" 
struct, I wouldn't have any feasible way to store a set of them.

The only workable idea I've had so far is to basically ape the 
Result structs from std.algorithm, replacing the aliased function 
with a function pointer. The pointer will be more costly than the 
alias, but it would be storable (and smaller and easier to manage 
than a whole bunch of temporary buffers).

I was hoping to get some opinion on options and alternatives for 
this situation from some experienced D coders. Maybe needing 
something like this to begin with is a code smell?
Jun 19 2014
parent reply "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
On Thu, Jun 19, 2014 at 08:02:39PM +0000, Vlad Levenfeld via
Digitalmars-d-learn wrote:
[...]
 For example, lets say I had a couple of managed arrays of doubles, and
 I had some computational path that, at some point, took these two
 ranges and zipped them before passing them along. At that node (where
 the zip is performed) I'd like to save a struct that, upon request,
 returns a Tuple!(double, double) range populated with the current
 values of the source double[]s. Then anything which uses that struct
 is only exposed to a range of ordered pairs, which fell from the sky
 as far as anything on the client-side of the interface is concerned.
 
 Of course I can't, not right off the bat, because storage requires
 specifying a type to store, and the actual type of a lazy-evaluated
 functional result depends on how it was constructed ("Voldemort types"
 as I've heard them called), so there's no way to, say, keep an array
 of these (unless they all have the same source types and underwent the
 same transformations). So even if I could create such a "midway node"
 struct, I wouldn't have any feasible way to store a set of them.
I've run into this before. The solution is to use a class-based wrapper from std.range to wrap around the range and expose a uniform external type: // Original version (doesn't compile): auto func(R)(R inputRange) { if (someCondition) return inputRange.map!((x)=>x+1); else return inputRange.filter!((x) => x > 10); // Compile error: map() and filter() don't return the // same type, so the return type of func() cannot be // determined! } // Solution: import std.range : inputRangeObject; InputRange!E func(R,E)(R inputRange) { if (someCondition) return inputRangeObject(inputRange.map!((x)=>x+1)); // N.B. the object returned by inputRangeObject // implements the InputRange!E interface. else return inputRangeObject(inputRange .filter!((x) => x > 10)); // N.B.: this object also implements the same // InputRange!E interface. // Since both branches implement the InputRange!E // interface, we can use that as the uniform external // type to represent either returned range. } This example, of course, illustrates the functional solution to the problem; in your case, you can just store the InputRange!E interfaces in your nodes, and you can assign different kinds of ranges to it, as long as they have a common element type. Exactly which concrete type is behind the InputRange!E interface reference can be determined at runtime; it doesn't have to be known at compile-time. T -- We are in class, we are supposed to be learning, we have a teacher... Is it too much that I expect him to teach me??? -- RL
Jun 19 2014
parent "Vlad Levenfeld" <vlevenfeld gmail.com> writes:
Thank you. This would work, however I'm wary of a class-based 
solution. InputRangeObject.save() calls new and it appears 
opSlice is unimplemented for some technical reasons... I think 
perhaps, since my case is more specialized than the use cases 
InputRangeObject is meant for, wrapping some kind of functor in a 
struct range implementation would be equivalent to this (minus 
the heap allocations, plus slicing) for my purposes.
Jun 19 2014