-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Refactor traits based on methods #35095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
It is still better to use value, not types. Working around inference limitation is strictly a problem for the caller of the function.
How does this allow things to be more easily combined? It seems that this is similar to say there should be only one function and all other functions should be implemented as method for this single function, |
Happy to change the proposal to
I think its more like saying there should be only one method table...but fair point. I can imagine a macro which would make writing these definitions easier. Something like:
|
Also, worth noting that I'm not proposing about adding it for every function, just every function that is required as part of an abstract interface. |
In that case, I assume you mean automatic, since an opt in behavior won't be any different from what's currently available, just with a diifferent syntax. However, if it's automatic, it'll basically be asking for a inferable version of |
yes, I am envisioning opt in behavior, and yes its just a syntax change, but syntax matters |
Sure, I agree syntax does matter. However, then the question is the claim you made initially.
which is what I asked in the first reply above. |
Because you can use logical operators on Bools (and typed bools provided the proper infrastructure). So you could write for example
|
You can already do that now.
Again, if this is just syntax change that's fine and most of the time I'll have little opinion on what the best syntax should be. However, if this is just a syntax change, then it'll be hard to say this is more flexible. It could be shorter, it could be more convinient for certain usage, but it won't allow you to do what's impossible before or in cases that it couldn't be used before. FWIW, this is also very closed to having |
Why? |
The return value carries more than two states, and some functions dispatches on particular combinations. This methods is requiring all functions to have only two states (i.e. one bit of info) so you'll need to pass on more traits in order to pass the same amount of information. In another word, what is possible now by passing |
You could dispatch on |
You could have a fallback method like:
|
Yes, and I never say it's slow. I say it's no faster than a branch and in this case the comparison is to other dispatch and there won't be much difference. Dispatchig on true/false does have additional problem though since you can quickly run into boolean hell with more and more traits involved all having the same value set. And it seems that you are indeed most interested in complex cases.
The producer of the info isn't the problem. The consumer is. If you are implementing something that dispatches on Now you have |
This seems like extra flexibility, not extra complexity, to me. Also, I think something more like:
would be better. |
It's equivalent functionality (i.e. not more flexible) and longer syntax (I'm only talking about syntax complexity in this case). I believe the latter objectively requires more argument. I'm making no argument on which one is better for this simple case, just that the complexity is higher.
Now this is't about syntax anymore, and no that's how you lost flexibility. It is of course a good way to implement pure logic on traits but you can't do that in general. This is exactly why we have dispatch at all and not just branching on type checks in user code. With that branch written, it is now impossible to extend the dispatch logic it creates which make it less flexible. Also note that since you are dealing with only one type and one kind of info in this case you can easily exhaust all possible values never require this to be extended, which is exactly why this is a good way to implement pure/simple trait computation. However, as long as you have any other dispatch/logic in the branch, those logic are now bound to the branch logic and cannot be extended freely anymore. In another word, the branch is a single dispatch on one argument, which is strictly less flexible than multiple dispatch. (edit: I have no idea why I keep typing "now" as "not"...) |
What do you mean? Can't you define new methods of
To repeat, if you want dispatch, you can use |
Yes, but that's not part of the dispatch logic (on trait values). You can have
Yes, which is why I was using dispatch in the previous response.
Well, if that's the case, then sure, the lack of information won't be a concern anymore. Though,
I also see why you might be confused about the relation between dispatch and branch. So to summarise and also mention why I brought up branch vs dispatch in different context.
(edit: and you can also generalize branch vs dispatch to normal value computation to type domain computation to cover the discussion after the original proposal on discourse was disgarded.) |
This seems pretty easy to do. What about defining a fallback method like
|
Sure, I agree. There's definitely traits that don't make quite as straightforward sense using this framework, like |
The general issue with this kind of approach is to keep the two in sync. Basically,
I saw you use "unify many" instead of "unify all" and I wasn't commenting on that. (Since it was a fair statement assuming everything else is accepted). I'm talking about how you use a particular trait. Are you using it for something as inflexiable/simple as a branch or are you taking advantage of multiple dispatch. This is not about syntnax and the particular trait in question doesn't matter... (in another word, I agree with what you've just said but that's not what I meant....) |
You mean
|
Dimension,
Well, the whole point of the direct compatibilty I mentioned above is to use |
Ah got it. I think the number of dimensions is just
What is boolean hell? Is there a three headed dog there? |
I believe so.
I was actually looking for a wiki article about it but couldn't find one. There's a few google hit so you can read about it there. I actually also have no idea where did I first about this term... Anyway, it's about using boolean to pass way too much information and you end up with functions that looks like Of course you can always write bad code like this without using And TBH, I might be using the wrong term. I searched "boolean hell" and found a few hit that is what I'm talking about and assumed that I'm not too off... |
Boolean hell just sounds like bad code hell to me. Which means there's a
Sounds a lot like the existing collect machinery in |
Yes, but since reducing the amount of information each parameter carries strongly encourages and even necessitate such usage the design of the argument is part of the bad code here... In fact, I think most of what I've read/seen/done about avoiding such a trap are exactly about replacing boolean flags with enums, in some sense the design of the argument type is almost all of it.
I would't call it easy to understand but it's far from what I'm talking about. The parameters are mostly following a defined order (so when you don't care about it you can assume you cann jut pass them in the same order you get them). The parameters are mostly of different types so when you do care about them there's no question what a function that has |
That's fair, and why I think inline use like
would be best |
The two are not comparible. There are cases, i.e. externally extendable multitple dispatch, that just can't be expressed this way. I'm only talking about the case you do need to pass trait value around here. |
I don't buy this argument. You can do
which is just as flexible as multiple dispatch |
There's very little to be argued here. If it was as flexible as dispatch we won't have the current dispatch system at all. As I already mentioned above, if you have control over everything, of course you'll know all the dispatch path when you write the code. However, that's simply not the case. Your package will most likely implement some fallback methods that uses the information you know, the function will look like And to repeat I've said before, you need to be clear if what you want to define using this is a dispatch or not. In another word, are you simply defining functions that takes |
Again, this means that you cannot do logic on them easily anymore. |
Yes you can. You could define
for example |
I have never written an API with |
That’s more like min or max. What should !Slow be?
The point is that there isn’t a single method for this. It’s a multiple method interface. |
It's a currently non existent interface. How do I figure out if a |
If this is useful enough one can be add, say calling it HasIsequalHash. Similar to indexing above, I’m using existing interface as examples as how traits for them need to be defined. |
I can't imagine a scenario when it would not be useful to know about the existence of API required methods. Can you? Regarding fast/slow, seems to me a good solution would be having a separate
|
Yes I can. It's when the API has property that isn't about existance of the method (i.e. when that's not what you are interesed in). You can obviously always still provide more information that's not useful, but having to use a completely different mechanism to query for different information isn't nice. Also, it's not even about figuring out the existence of "methods". It's about it being "methods" rather than a "method".
See above, I agree you can always encode more information by just using more bits. However, that's not a very good solution as soon as you want to implement dispatch. |
For example?
Two bits seems pretty good to me |
The type parameter for
Not sure what you are talking about because you don't have two bits of information. You only have one bit per trait. If you meant that you think using two bits is fine then that's not the problem either. The issue is you need twice (or more) as many argument. |
We've been through this.
You don't need any arguments. You just need |
Well, I thought you weren't suggesting to actually use it that way. For one, this force you to actualy use the value so you can't do things in the type. Also, this is exactly what I meant that,
Again, you can't use branch for dispatch. And again, if you are just talking about something that makes writing complex branch easier then it's fine. If you are talking about replacing trait on all what it can be used for then no, you can't use branches to replace dispatch and the necessity of putting things through arguments. |
Yes, that's what I'm talking about |
In that case please update the issue since things like
and
are very misleading when what you are offering aren't to replace all what those are used for. |
I'm totally lost. I'm talking about refactoring of all traits that only check for the existence of a method as logical. This makes complex branching easier. |
I'm saying that as of now, a lot of the traits are used for dispatch (they are type domain computations and if you don't have to do dispatch you would not put these in type domain to start with). What you propose makes these usage much harder and you are not targetting this usage. Because of that, you should not advertise this as replacing traits because you can't do that. |
You can use True and False for dispatch |
Listen, we're going around in circles, and even if you did happen to convince me I'm wrong, I wouldn't stop arguing because your elitist attitude towards programming and the way you treat people makes me angry. |
Well, you've already accepted that you are just interesed in operation on these and not dispatch. I also thought you already realized all the issues if you use those generic values for dispatch. For review, see, #35095 (comment) To summarize, yes, you can use generic value for dispatch and that's what leads to boolean hell. What you are proposing is a very bad way for the current usage of trait (I'm specifically tallking about the property of returning generic value from traits, not any aspect of it) |
OK, I take it as you are intentially going in circle here and make me repeating my argument for the sole purpose of keeping the argument going. I think that's a clear enough sign that this issue can be closed. |
You can use |
I did not say any of them are boolean hell, I'm saying that it's how you get there, because
And again, I'm saying you are at least doubling the number of argument for dispatch here. It's not going to be too much a problem when you have only one argument, which is most of the cases you brought up. It's only an issue when you are doing multiple dispatch. FWIW,
This is not true. In both #35095 (comment) and #35095 (comment), I was explicitly talking about cases where more than one arguments and two bits of information is needed. |
Yes, there's a slight increase in number of arguments compared to the existing trait system (I'm saying slight because I think having to check the speed of more than one method would be very rare), but at the expense of a consistent interface which can be extended arbitrarily without any new types |
I've talked about this above as well. The point is that you are dispatching on the properties available. It's likely that most method will only use few bit of info, but the fact that some may need it means that you have to pass it around. |
Yes |
Closed in favor of #35940 |
All traits which semantically define whether or not a method is expected, e.g.
HasShape
,HasLength
,HasEltype
, could be refactored into a single function:expect_method(::typeof(size), ...)
expect_method(::typeof(length), ...)
expect_method(::typeof(eltype), ...)
which need only take two typed values:
True
andFalse
. This could unify many existing trait interfaces, and allow them to be more easily combined. In addition, I proposeexpect_method(::typeof(getindex), ::Type{Container}, ::Type{Vararg{Int, Dimensions}})
expect_method(::typeof(setindex!), ::Type{Container}, ::Type{Element}, ::Type{Vararg{Int, Dimensions}})
etc. for any method that is required by an Abstract interface.
The text was updated successfully, but these errors were encountered: