digitalmars.D.learn - non empty slices
- Alex (46/46) Jun 02 2016 Ok, a strange question from my side again...
- ag0aep6g (25/41) Jun 02 2016 A little terminology: "Slice" is not a synonym for "range". iota does
- Alex (16/43) Jun 02 2016 So this would mean, the contract I use now for run-time check is
- ag0aep6g (75/82) Jun 02 2016 I may be getting what you're up to. Maybe not.
- Alex (9/86) Jun 02 2016 Yes! :)
- Alex (18/34) Jun 02 2016 Just tried this instead of your f-function:
- ag0aep6g (14/31) Jun 02 2016 Yeah, can't do it that way. You have only one f_impl call, but want it
- ag0aep6g (11/17) Jun 02 2016 I just made those up on the spot. Note that Many is not actually
Ok, a strange question from my side again... Let's begin, with what works: Say, I define two types: import std.typecons : Nullable; alias M = uint; alias Mr = Nullable!M; then, I can write two types of methods. Those which can handle Mr's: void fun1(Mr val) { //do some funny stuff } and those, which assume an existing value is provided: void fun2(M val) { //do some other funny stuff } they could be overloaded by using the same name I think... but this is not the point. The question is, how to define the same thing for ranges. What I started with is: import std.range : iota; alias MrSlice = typeof(iota(M.init)); so far, so good. The MrSlice-thing is the analogy for Mr, as it can be empty, as Mr can. What I also need is the analogy for the M-thing. But how can I define a slice, which is not-empty by definition? A workaround, which works is for example, using in a function which assumes a non-empty slice a contract: void fun3(MrSlice val) in { assert(!val.empty); } body { // do some funny stuff, part 3 :) } I tried also something using Algebraic types with an underlying int, but couldn't manage to append elements to something like: alias List2 = Algebraic!(This[]); or alias List(Leaf) = Algebraic!(Leaf, This*); The cool thing is, if the algebraic stuff works, it would potentially be able to replace all four aliases altogether... But a knock-out criteria is: the solution has to be nogc
Jun 02 2016
On 06/02/2016 03:37 PM, Alex wrote:The question is, how to define the same thing for ranges. What I started with is: import std.range : iota; alias MrSlice = typeof(iota(M.init)); so far, so good. The MrSlice-thing is the analogy for Mr, as it can be empty, as Mr can. What I also need is the analogy for the M-thing. But how can I define a slice, which is not-empty by definition?A little terminology: "Slice" is not a synonym for "range". iota does not return a slice, it returns a range. "Slice" is being used as a synonym for "dynamic array" (Type[]), and in the context of the slicing operator (expression[] or expression[a .. b]). Now, for the actual question - how to define a range that is non-empty by definition: One of the range primitives is popFront. popFront removes one element from the range. popFront cannot change the type of the range. So you can't have a type that is a range, and is guaranteed not to be empty, and can be emptied by calling popFront. But you can have a type that is a range, is guaranteed not to be empty, and *cannot* be emptied by calling popFront: an infinite range. That's a range that never gets empty no matter how often popFront is called. The library recognizes infinite ranges when you define `empty` like this: `enum bool empty = false;`. There's std.range.primitives.isInfinite [1] to check for them. I guess that's not really what you're looking for. I don't think you can otherwise statically check that a range isn't empty. You could probably make a wrapper that checks once on construction, but that's still a run-time check. And I don't see it being particularly useful, because popFront is an essential parts of ranges. [...]I tried also something using Algebraic types with an underlying int, but couldn't manage to append elements to something like: alias List2 = Algebraic!(This[]); or alias List(Leaf) = Algebraic!(Leaf, This*); The cool thing is, if the algebraic stuff works, it would potentially be able to replace all four aliases altogether... But a knock-out criteria is: the solution has to be nogcYou've lost me there. I don't see how non-empty range and Algebraic connect. [1] https://dlang.org/phobos/std_range_primitives.html#.isInfinite
Jun 02 2016
On Thursday, 2 June 2016 at 14:31:15 UTC, ag0aep6g wrote:A little terminology: "Slice" is not a synonym for "range". iota does not return a slice, it returns a range. "Slice" is being used as a synonym for "dynamic array" (Type[]), and in the context of the slicing operator (expression[] or expression[a .. b]). Now, for the actual question - how to define a range that is non-empty by definition: One of the range primitives is popFront. popFront removes one element from the range. popFront cannot change the type of the range. So you can't have a type that is a range, and is guaranteed not to be empty, and can be emptied by calling popFront. But you can have a type that is a range, is guaranteed not to be empty, and *cannot* be emptied by calling popFront: an infinite range. That's a range that never gets empty no matter how often popFront is called. The library recognizes infinite ranges when you define `empty` like this: `enum bool empty = false;`. There's std.range.primitives.isInfinite [1] to check for them. I guess that's not really what you're looking for.Yes, that's not. :)I don't think you can otherwise statically check that a range isn't empty. You could probably make a wrapper that checks once on construction, but that's still a run-time check. And I don't see it being particularly useful, because popFront is an essential parts of ranges.So this would mean, the contract I use now for run-time check is ok. And... well... maybe I can alias two different methods, one which returns an arbitrary range and one which returns a range, checked for emptiness in an out contract... This already would save a lot of code duplication...You've lost me there. I don't see how non-empty range and Algebraic connect.What I mean is: if there would be a possibility to use algebraic types here (or maybe some kind of template...), then my types would be able (maybe! did not seen anything similar yet...) to choose the proper methods for themselves automatically: As long as my type has a length > 1 it is handled as a range, if the range has the length=1 it is handled as a single element and not as a range, if the range has the length=0 it is handled as an absent element.
Jun 02 2016
On 06/02/2016 05:16 PM, Alex wrote:What I mean is: if there would be a possibility to use algebraic types here (or maybe some kind of template...), then my types would be able (maybe! did not seen anything similar yet...) to choose the proper methods for themselves automatically: As long as my type has a length > 1 it is handled as a range, if the range has the length=1 it is handled as a single element and not as a range, if the range has the length=0 it is handled as an absent element.I may be getting what you're up to. Maybe not. So we start with something like this (using arrays instead of arbitrary ranges to keep things simple): import std.stdio: writeln; void main() { f([]); f([1]); f([1, 2]); } void f(int[] a) { if (a.length == 0) f_none(a); else if (a.length == 1) f_one(a); else f_many(a); } void f_none(int[] a) { writeln("nothing to see here"); } void f_one(int[] a) { assert(a.length == 1); writeln("exactly one: ", a[0]); } void f_many(int[] a) { assert(a.length >= 2); writeln("at least two: ", a[0 .. 2]); } And instead you'd like something more like this: import std.stdio: writeln; import std.variant: Algebraic; void main() { f([]); f([1]); f([1, 2]); } void f(int[] arr) { A a = arrayToA(arr); foreach (T; A.AllowedTypes) { if (T* p = a.peek!T) f_impl(*p); } } void f_impl(Empty e) { writeln("nothing to see here"); } void f_impl(int a) { writeln("exactly one: ", a); } void f_impl(Many!int a) { writeln("at least two: ", a[0 .. 2]); } struct Empty {} struct Many(T) { T[] arr; alias arr this; /* Somehow it's enforced here that arr.length >= 2. */ } alias A = Algebraic!(Empty, int, Many!int); A arrayToA(int[] a) { A result; switch (a.length) { case 0: result = Empty.init; break; case 1: result = a[0]; break; default: result = Many!int(a); } return result; } And the advantages of it are: * Don't need asserts in the different `f_impl`s. * The branching in f is guided by the parameter types of f_impl (could possibly be extracted, instead of being hardcoded manually). Tell me if I'm way off.
Jun 02 2016
On Thursday, 2 June 2016 at 16:21:03 UTC, ag0aep6g wrote:On 06/02/2016 05:16 PM, Alex wrote: I may be getting what you're up to. Maybe not. So we start with something like this (using arrays instead of arbitrary ranges to keep things simple): import std.stdio: writeln; void main() { f([]); f([1]); f([1, 2]); } void f(int[] a) { if (a.length == 0) f_none(a); else if (a.length == 1) f_one(a); else f_many(a); } void f_none(int[] a) { writeln("nothing to see here"); } void f_one(int[] a) { assert(a.length == 1); writeln("exactly one: ", a[0]); } void f_many(int[] a) { assert(a.length >= 2); writeln("at least two: ", a[0 .. 2]); } And instead you'd like something more like this: import std.stdio: writeln; import std.variant: Algebraic; void main() { f([]); f([1]); f([1, 2]); } void f(int[] arr) { A a = arrayToA(arr); foreach (T; A.AllowedTypes) { if (T* p = a.peek!T) f_impl(*p); } } void f_impl(Empty e) { writeln("nothing to see here"); } void f_impl(int a) { writeln("exactly one: ", a); } void f_impl(Many!int a) { writeln("at least two: ", a[0 .. 2]); } struct Empty {} struct Many(T) { T[] arr; alias arr this; /* Somehow it's enforced here that arr.length >= 2. */ } alias A = Algebraic!(Empty, int, Many!int); A arrayToA(int[] a) { A result; switch (a.length) { case 0: result = Empty.init; break; case 1: result = a[0]; break; default: result = Many!int(a); } return result; } And the advantages of it are: * Don't need asserts in the different `f_impl`s.Yes.* The branching in f is guided by the parameter types of f_impl (could possibly be extracted, instead of being hardcoded manually).Yes! :)Tell me if I'm way off.You totally hit the point! The cool thing about the Algebraic is as I expected, that it doesn't change it's type... And the hard thing is, that I'm not used to its Empty, Many, ... things yet. But the question remains how to keep this nogc? I wonder at the line with peek... and why it is not just returning the value...
Jun 02 2016
On Thursday, 2 June 2016 at 20:11:21 UTC, Alex wrote:On Thursday, 2 June 2016 at 16:21:03 UTC, ag0aep6g wrote:Just tried this instead of your f-function: void f(int[] arr) { A result; import std.meta; alias TL = AliasSeq!(Empty, int, Many!int); int caseS; switch (arr.length) { case 0: result = Empty.init; caseS = 0; break; case 1: result = arr[0]; caseS = 1; break; default: result = Many!int(arr); caseS = 2; } f_impl(*result.get!(TL[caseS])); } But got: Error: variable caseS cannot be read at compile time which is obviously true...void f(int[] arr) { A a = arrayToA(arr); foreach (T; A.AllowedTypes) { if (T* p = a.peek!T) f_impl(*p); } }You totally hit the point! The cool thing about the Algebraic is as I expected, that it doesn't change it's type... And the hard thing is, that I'm not used to its Empty, Many, ... things yet. But the question remains how to keep this nogc? I wonder at the line with peek... and why it is not just returning the value...
Jun 02 2016
On 06/02/2016 11:37 PM, Alex wrote:Just tried this instead of your f-function: void f(int[] arr) { A result; import std.meta; alias TL = AliasSeq!(Empty, int, Many!int); int caseS; switch (arr.length) { case 0: result = Empty.init; caseS = 0; break; case 1: result = arr[0]; caseS = 1; break; default: result = Many!int(arr); caseS = 2; } f_impl(*result.get!(TL[caseS])); } But got: Error: variable caseS cannot be read at compile time which is obviously true...Yeah, can't do it that way. You have only one f_impl call, but want it to go to different overloads based on dynamic information (caseS). That doesn't work. You need three different f_impl calls. You can generate them, so there's only one in the source, but it's a bit involved: sw: switch (caseS) { foreach (i, T; TL) { case i: f_impl(result.get!T); break sw; } default: assert(false); }
Jun 02 2016
On Thursday, 2 June 2016 at 22:17:32 UTC, ag0aep6g wrote:Yeah, can't do it that way. You have only one f_impl call, but want it to go to different overloads based on dynamic information (caseS). That doesn't work. You need three different f_impl calls. You can generate them, so there's only one in the source, but it's a bit involved: sw: switch (caseS) { foreach (i, T; TL) { case i: f_impl(result.get!T); break sw; } default: assert(false); }Oh... wow... cool! :) But still, I can't mark the f-method nogc, and this is not due to the writeln calls... why GC is invoked, although everything is known and no memory allocation should happen?
Jun 02 2016
On 06/03/2016 01:17 AM, Alex wrote:But still, I can't mark the f-method nogc, and this is not due to the writeln calls... why GC is invoked, although everything is known and no memory allocation should happen?It's the Algebraic. The `get` method isn't nogc. The documentation [1] says that it may throw an exception, which is most probably being allocated through the GC. So that's a reason why it can't be nogc. The alternative `peek` method is not documented to throw an exception, but it's not nogc either. No idea why. Maybe Algebraic does GC allocations internally. I wouldn't know for what, though. Or it misses a nogc somewhere.
Jun 02 2016
On Thursday, 2 June 2016 at 23:35:53 UTC, ag0aep6g wrote:It's the Algebraic. The `get` method isn't nogc. The documentation [1] says that it may throw an exception, which is most probably being allocated through the GC. So that's a reason why it can't be nogc. The alternative `peek` method is not documented to throw an exception, but it's not nogc either. No idea why. Maybe Algebraic does GC allocations internally. I wouldn't know for what, though. Or it misses a nogc somewhere.Yeah... thanks a lot!
Jun 02 2016
On 06/03/2016 01:35 AM, ag0aep6g wrote:The alternative `peek` method is not documented to throw an exception, but it's not nogc either. No idea why. Maybe Algebraic does GC allocations internally. I wouldn't know for what, though. Or it misses a nogc somewhere.I've looked at the source to see if it's something simple, and Algebraic/VariantN seems to be terribly complicated. Writing a simpler nogc tagged union may be easier than fixing the phobos one, if the phobos one can even be made nogc.
Jun 02 2016
On Thursday, 2 June 2016 at 23:44:49 UTC, ag0aep6g wrote:On 06/03/2016 01:35 AM, ag0aep6g wrote:I'm also inside the source... yes, its not a simple one. I think I will try to write my own one...The alternative `peek` method is not documented to throw an exception, but it's not nogc either. No idea why. Maybe Algebraic does GC allocations internally. I wouldn't know for what, though. Or it misses a nogc somewhere.I've looked at the source to see if it's something simple, and Algebraic/VariantN seems to be terribly complicated. Writing a simpler nogc tagged union may be easier than fixing the phobos one, if the phobos one can even be made nogc.
Jun 02 2016
On 06/02/2016 10:11 PM, Alex wrote:The cool thing about the Algebraic is as I expected, that it doesn't change it's type... And the hard thing is, that I'm not used to its Empty, Many, ... things yet.I just made those up on the spot. Note that Many is not actually implemented at all. There is no check that the array has at least two elements. And Empty is just there, because I needed a type for the Algebraic.But the question remains how to keep this nogc?Apparently, it's Algebraic that isn't nogc. I don't know what it allocates. Maybe it allocates space for large types (but there aren't any here), or maybe it can throw a GC-allocated exception.I wonder at the line with peek... and why it is not just returning the value...I wouldn't expect that to be the problem with nogc. As far as I see, the pointer is used as a way to return "not found" in the form of null. When you get a non-null pointer it's probably just into the Algebraic.
Jun 02 2016