digitalmars.D.learn - how to store a range transformed by a range function?
- Vlad Levenfeld (39/39) Jun 19 2014 I've got an app that uses a custom range type over some
- H. S. Teoh via Digitalmars-d-learn (41/57) Jun 19 2014 I've run into this before. The solution is to use a class-based wrapper
- Vlad Levenfeld (7/7) Jun 19 2014 Thank you. This would work, however I'm wary of a class-based
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
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
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