August 03, 2016 Re: Indexing with an arbitrary type | ||||
---|---|---|---|---|
| ||||
Posted in reply to Alex | On Wednesday, 3 August 2016 at 14:46:25 UTC, Alex wrote: > On Wednesday, 3 August 2016 at 14:23:59 UTC, Jonathan M Davis wrote: > > But it should. > > Just found this: > https://www.sgi.com/tech/stl/StrictWeakOrdering.html > which should be fulfilled by a type, which can be used as a key. So, in my opinion, the property "being a key" is a matter of an abstract definition and should have nothing to do with objects which I want to index with it. > > Sure, the object which I want to index, has to define an opIndex method, which can receive the key-thing, but this is already the next step. So, I think this is the answer to my question: http://stackoverflow.com/questions/27282568/d-template-constraint-to-show-whether-a-given-type-is-comparable |
August 03, 2016 Re: Indexing with an arbitrary type | ||||
---|---|---|---|---|
| ||||
Posted in reply to Alex | On Wednesday, August 03, 2016 16:42:18 Alex via Digitalmars-d-learn wrote:
> On Wednesday, 3 August 2016 at 14:46:25 UTC, Alex wrote:
> > On Wednesday, 3 August 2016 at 14:23:59 UTC, Jonathan M Davis wrote:
> >
> > But it should.
> >
> > Just found this:
> > https://www.sgi.com/tech/stl/StrictWeakOrdering.html
> > which should be fulfilled by a type, which can be used as a
> > key. So, in my opinion, the property "being a key" is a matter
> > of an abstract definition and should have nothing to do with
> > objects which I want to index with it.
> >
> > Sure, the object which I want to index, has to define an opIndex method, which can receive the key-thing, but this is already the next step.
>
> So, I think this is the answer to my question: http://stackoverflow.com/questions/27282568/d-template-constraint-to-show-wh ether-a-given-type-is-comparable
int is comparable, but it's not going to index float[string]. Only a string is going to do that. Similarly, long is comparable, but on a 32-bit system, it won't index int[], because it's larger than size_t, which is the actual index type for int[].
If all that your code cares about is that type T is comparable, then sure, test that it's comparable. But if it's going to pass a value of type T to the index operator of type U, then T's index operator needs to accept type U. And a type like SysTime from std.datetime is comparable, but it sure isn't going to work as an index for int[].
And being comparable isn't enough to guarantee that T is going to work.
In fact, there isn't even a guarantee that U's opIndex even takes a type
that's comparable. Maybe it's a weird socket type that use opIndex to
connect to a particular IP address rather than having a properly named
function for that, and it takes an Address object that isn't comparable.
Yes, that would be weird and likely bad design, but there's nothing about
opIndex that prevents it. So, testing for whether U was comparable wouldn't
necessarily tell you whether it could be used to index a type.
But even if everyone restricts themselves to using opIndex in sane ways, and all index types are comparable, not all types take the same index types. So, if you just test that the index type is comparable without testing it against the type that it's actually going to index, then your template constraint will let through types that won't be compatible and will result in a compilation error.
Maybe I don't understand enough about what you're trying to do and am not giving the best advice as a result, but if you have a function that takes a two arguments where one is supposed to be an index, and the other is supposed to be an object to be indexed, then the only template constraint that will prevent types getting through which won't compile when the code actually uses the index operator is to test that the object to be indexed can be indexed by the index object. Testing that the index type is comparable is completely insufficient. It will work with a particular set of types, but it will not work with all indexable types, and if a types that don't work together are passed to that function, then you will get a compilation error inside of the function instead of at the template constraint catching it.
- Jonathan M Davis
|
August 03, 2016 Re: Indexing with an arbitrary type | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Wednesday, 3 August 2016 at 17:56:54 UTC, Jonathan M Davis wrote: > int is comparable, but it's not going to index float[string]. Only a string is going to do that. Similarly, long is comparable, but on a 32-bit system, it won't index int[], because it's larger than size_t, which is the actual index type for int[]. > > If all that your code cares about is that type T is comparable, then sure, test that it's comparable. But if it's going to pass a value of type T to the index operator of type U, then T's index operator needs to accept type U. And a type like SysTime from std.datetime is comparable, but it sure isn't going to work as an index for int[]. This is a good point, I admit. But still, isn't it the indexable objects task to define the opIndex method to take a particular index? > And being comparable isn't enough to guarantee that T is going to work. > In fact, there isn't even a guarantee that U's opIndex even takes a type > that's comparable. Maybe it's a weird socket type that use opIndex to > connect to a particular IP address rather than having a properly named > function for that, and it takes an Address object that isn't comparable. > Yes, that would be weird and likely bad design, but there's nothing about > opIndex that prevents it. So, testing for whether U was comparable wouldn't > necessarily tell you whether it could be used to index a type. Yes. I tested a struct, which has a Nullable value in it aliased to this and with an opCmp defined with respect to the Nullable value and it didn't worked, too. I find this strange, because if I already define, how the value should behave, if it is in the null state, I would assume, it should be able to index with it too... > But even if everyone restricts themselves to using opIndex in sane ways, and all index types are comparable, not all types take the same index types. So, if you just test that the index type is comparable without testing it against the type that it's actually going to index, then your template constraint will let through types that won't be compatible and will result in a compilation error. Yes. But this is just a compilation error... It says, well, there is a mistake in the code. What I want is to achieve a constraint on my own type, which is at this time point defined just as alias MyType = size_t; but later on, this could maybe become a struct, and I still want to be able to handle it like an index. The opIndex methods of the surrounding objects are already defined in terms of the MyType, so the problem of interacting does not play a big role. The question is how to describe the minimal requirement on the type itself... > Maybe I don't understand enough about what you're trying to do and am not giving the best advice as a result, but if you have a function that takes a two arguments where one is supposed to be an index, and the other is supposed to be an object to be indexed, then the only template constraint that will prevent types getting through which won't compile when the code actually uses the index operator is to test that the object to be indexed can be indexed by the index object. Testing that the index type is comparable is completely insufficient. Yes... I can confirm this... the last line of this code inside the main gives an error... https://dpaste.dzfl.pl/043d1deb9073 > It will work with a particular set of types, but it will not work with all indexable types, and if a types that don't work together are passed to that function, then you will get a compilation error inside of the function instead of at the template constraint catching it. The code above, gives even a runtime error, which is much worse... It would be enough, I think, to assert, that MyType resolves to the particular set of types... Which are these? Is it possible to write this kind of template? |
August 04, 2016 Re: Indexing with an arbitrary type | ||||
---|---|---|---|---|
| ||||
Posted in reply to Alex | What I think about is something like this: https://dpaste.dzfl.pl/d37cfb8e513d |
August 05, 2016 Re: Indexing with an arbitrary type | ||||
---|---|---|---|---|
| ||||
Posted in reply to Alex | On Thursday, August 04, 2016 08:13:59 Alex via Digitalmars-d-learn wrote:
> What I think about is something like this: https://dpaste.dzfl.pl/d37cfb8e513d
Okay, you have
enum bool isKey(T) = is(typeof(T.init < T.init) : bool);
template isNullable(T) { enum isNullable = __traits(compiles, T.init.isNull);
}
struct IndexAble
{
int[] arr;
this(size_t size)
{
arr.length = size;
}
ref int opIndex(T)(T ind) if(isKey!T)
{
static if(isNullable!T)
if(ind.isNull) return arr[$]; // or another manually defined state.
return arr[ind];
}
}
What happens if I do this?
void main()
{
IndexAble indexable;
indexable["hello"];
}
You get a compilation error like
q.d(17): Error: cannot implicitly convert expression (ind) of type string to
ulong
q.d(24): Error: template instance q.IndexAble.opIndex!string error
instantiating
because the index type of int[] is size_t, so it's only going to accept values that are implicitly convertible to size_t, and string is not implicitly convertible to size_t. opIndex's template constraint did not catch the fact that passing "hello" to opIndex would result in a compilation error. string is perfectly comparable, and it's not a valid index type for int[] - and comparability is all that opIndex's template constraint checks for.
For opIndex to actually catch that an argument is not going to compile if opIndex is instantiated with that type, then its template constraint needs to either test that the index type will implicitly convert to size_t so that it can be used to index the int[], or it needs to test that indexing int[] with the argument will compile. So, something like
ref int opIndex(T)(T ind)
if(is(T : size_t))
{...}
or
ref int opIndex(T)(T ind)
if(__traits(compiles, arr[ind]))
{...}
And given that you're dealing explicitly with int[] and not an arbitrary type, it really makes more sense to use is(T : size_t). If IndexAble were templated on the type for arr, then you would need to do the second. But testing for comparability is completely insufficient for testing whether the type can be used as an index. In fact, it's pretty much irrelevant. What matters is whether the index type will compile with the code that it's being used in inside the template. If you're forwarding the index variable to another object's opIndex, then what matters is whether that opIndex will compile with the index, and if you're doing something else with it, then what matters is that it compiles with whatever it is that you're doing with it.
I could declare a hash map implementation which did not use opCmp but just used opEquals and toHash. Then I could have a struct like
struct S
{
int i;
int j;
size_t toHash()
{
return i * j % size_t.max;
}
}
It has an opEquals (the implicit one declared by the compiler). It has a toHash. It does not have opCmp (and the compiler won't generate one). So, it's not comparable. But it would work in my hash map type only used toHash and opEquals and not opCmp.
HashMap!(S, string) hm;
hm[S(5, 22)] = "hello world";
And actually, while D's built-in AA's used to use opCmp (and thus required it), they don't anymore. So, that code even works with the built-in AA's. e.g.
string[S] aa;
aa[S(5, 22)] = "hello world";
So, comparability has nothing to do with whether a particular type will work as the key for an indexable type. What matters is that the index operator be given an argument that will compile with it - which usually means a type which is implicitly convertible to the type that it uses for its indices but in more complex implementations where opIndex might accept a variety of types for whatever reason, then what matters is whether the type that you want to pass it will compile with it. And unless all that that particular opIndex implentation needs to care about for a type to work with it is that the type is comparable, then testing that the type that you're going to give it is comparable is insufficient and may not even be relevant.
- Jonathan M Davis
|
August 06, 2016 Re: Indexing with an arbitrary type | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Friday, 5 August 2016 at 16:37:14 UTC, Jonathan M Davis wrote: > On Thursday, August 04, 2016 08:13:59 Alex via Digitalmars-d-learn wrote: >> What I think about is something like this: https://dpaste.dzfl.pl/d37cfb8e513d > > Okay, you have > > enum bool isKey(T) = is(typeof(T.init < T.init) : bool); > template isNullable(T) { enum isNullable = __traits(compiles, T.init.isNull); > } > > struct IndexAble > { > int[] arr; > this(size_t size) > { > arr.length = size; > } > > ref int opIndex(T)(T ind) if(isKey!T) > { > static if(isNullable!T) > if(ind.isNull) return arr[$]; // or another manually defined state. > > return arr[ind]; > } > } > > What happens if I do this? > > void main() > { > IndexAble indexable; > indexable["hello"]; > } > > You get a compilation error like > > q.d(17): Error: cannot implicitly convert expression (ind) of type string to > ulong > q.d(24): Error: template instance q.IndexAble.opIndex!string error > instantiating > > because the index type of int[] is size_t, so it's only going to accept values that are implicitly convertible to size_t, and string is not implicitly convertible to size_t. Yes. You are surely right. > opIndex's template constraint did not catch the fact that passing "hello" to opIndex would result in a compilation error. string is perfectly comparable, and it's not a valid index type for int[] - and comparability is all that opIndex's template constraint checks for. Right. My intension was to find the one check, which has to be fulfilled by all types, which could be used as an index... > > For opIndex to actually catch that an argument is not going to compile if opIndex is instantiated with that type, then its template constraint needs to either test that the index type will implicitly convert to size_t so that it can be used to index the int[], Which would be too restrictive, as we have associative arrays... > or it needs to test that indexing int[] with the argument will compile. So, something like I see the technical point, but the question which I want to answer with the isKey template is a different one. Maybe, a better formulation would be: "is a type uniquely convertible to size_t". > > ref int opIndex(T)(T ind) > if(is(T : size_t)) > {...} > > or > > ref int opIndex(T)(T ind) > if(__traits(compiles, arr[ind])) > {...} > > And given that you're dealing explicitly with int[] and not an arbitrary type, it really makes more sense to use is(T : size_t). If IndexAble were templated on the type for arr, then you would need to do the second. But testing for comparability is completely insufficient for testing whether the type can be used as an index. I think, if I separate the concern of "can use sth. as key" and "how to use sth. as key" the isKey template is sufficient for the first one. Sure, without providing the answer for what... > In fact, it's pretty much irrelevant. What matters is whether the index type will compile with the code that it's being used in inside the template. If you're forwarding the index variable to another object's opIndex, then what matters is whether that opIndex will compile with the index, and if you're doing something else with it, then what matters is that it compiles with whatever it is that you're doing with it. Right. I just wanted to clarify for myself, what is the minimum requirement for a "key" is. Meanwhile, I'm close to the view, that it has to provide some conversion to size_t, providing toHash is maybe nothing else... > I could declare a hash map implementation which did not use opCmp but just used opEquals and toHash. Then I could have a struct like > > struct S > { > int i; > int j; > > size_t toHash() > { > return i * j % size_t.max; > } > } > > It has an opEquals (the implicit one declared by the compiler). It has a toHash. It does not have opCmp (and the compiler won't generate one). So, it's not comparable. But it would work in my hash map type only used toHash and opEquals and not opCmp. > > HashMap!(S, string) hm; > hm[S(5, 22)] = "hello world"; > > And actually, while D's built-in AA's used to use opCmp (and thus required it), they don't anymore. So, that code even works with the built-in AA's. e.g. > > string[S] aa; > aa[S(5, 22)] = "hello world"; > Yeah. But you have either a perfect hashing and the absence of opCmp is the point what I'm wondering about. Or, the hashing is not perfect and then the usage of a key type relies on handling of collisions. > So, comparability has nothing to do with whether a particular type will work as the key for an indexable type. What matters is that the index operator be given an argument that will compile with it - which usually means a type which is implicitly convertible to the type that it uses for its indices but in more complex implementations where opIndex might accept a variety of types for whatever reason, then what matters is whether the type that you want to pass it will compile with it. At the end - yes, I totally agree :) > And unless all that that particular opIndex implentation needs to care about for a type to work with it is that the type is comparable, then testing that the type that you're going to give it is comparable is insufficient and may not even be relevant. Ok, the relevance is a good point... And yes... you are probably right. As a complement: I surely can't define an opIndex operator on all types, which come through my isKey template. Nor, the template can check, if a type can be used as an index for something. Even if I make a templated opIndex, I would not know, how to handle all value, which comes as an input. I think, what the template is good for is to check, if it is possible to define an opIndex in the terms of a type at all, and providing a hash method is just a workaround for not providing a strict comparison. |
Copyright © 1999-2021 by the D Language Foundation