Open laszlokindrat opened 4 months ago
Idea: If we were to introduce traits to each operator, could we name them Has**
instead of **able
?
I'm not suggesting that we change what we have, which are perfectly good (like Hashable
, Copyable
, etc.). Rather, I propose that we could introduce "syntactic traits" which, by itself, does not have any predefined semantics, such as having a __add__
method. We shouldn't even add these trait to the conforming types. This is very different from a numerical trait like Additive
, which also requires a __add__
method.
In python, some functions are only available in the operator
module. For example operator.index()
. In Mojo, index()
is builtin and imported everywhere, but it's not very practical, as index
is a common name for a variable/argument. Putting it in an operator
module would help clean that up.
Has**
etc instead of**able
idea: Can**
And somehow make this even shorter? for example for types that are used often
trait CanCollect(CanCopy, CanMove, CanDelete):
...
feels more natural
fn some_func[T: CanMove | CanCopy](somthing: T):
@parameter
if T.can[CanCopy]():
...
else:
...
In python, some functions are only available in the
operator
module. For exampleoperator.index()
. In Mojo,index()
is builtin and imported everywhere, but it's not very practical, asindex
is a common name for a variable/argument. Putting it in anoperator
module would help clean that up.
+1
Idea: If we were to introduce traits to each operator, could we name them
Has**
instead of**able
?I'm not suggesting that we change what we have, which are perfectly good (like
Hashable
,Copyable
, etc.). Rather, I propose that we could introduce "syntactic traits" which, by itself, does not have any predefined semantics, such as having a__add__
method. We shouldn't even add these trait to the conforming types. This is very different from a numerical trait likeAdditive
, which also requires a__add__
method.
Also +1, I like the expression "syntactic traits" for this. I think we would use these to build common compositions that some types would actually conform to explicitly (t.g. EqualityComparable
), but explicit conformance to just HasLessThan
will be rare. For this reason I also suggest that we call it Has*
instead of Can*
, since these traits really only just promise the existence of a method.
Regarding testing, what if we have things like this:
struct MockLessThan(HasLessThan):
fn __lt__(self, other: Self) -> Bool:
return True
def test_less_than():
var m = MockLessThan()
var r = m.__lt__(m)
assert_true(_type_is_eq[__type_of(r), Bool]())
assert_true(r)
I'm looking for minimal ways to "glue" these traits and their methods in the module, so that we don't accidentally move/remove them.
I think we would use these to build common compositions that some types would actually conform to explicitly
I sitll think Can**
will be shorter for composite traits e.g. StringableCollectionElement
becomes CanCollect & HasStr
, because Has**
could be reserved for base traits ("syntactic traits"), and concatenating is shorter as well CanCompareCollect
.
I like the idea of having these base traits a lot because once we are able to do T: HasCopy & HasGreaterThan
we will unlock true interface conformance regardless of types or inheritance or composition.
It might make sense to just use the dunder method name as the trait name. So there could be an __int__
trait and a __len__
trait etc.
Hmmm... although it's not explicitly stated in the style guide, to my understanding, the convention is camel case for types and traits.
Also, say for example that Formattable
(a type having a fn format_to(self, inout writer: Formatter)
method) were to be renamed as format
, that would conflict with the function name or potentially variable names. And a third aspect would be that underscore is normally used to denote that something is for private use. And a fourth albeit very subjective opinion, is that I think HasInt
, HasLen
, HasAdd
, HasSub
, HasEq
, HasGe
read a bit more naturally (I would even dare say more Pythonic (?) ).
I'm not sure why you're talking about renaming Formattable
to a lowercase name. All I'm suggesting is that it might be worth using special names for the traits that correspond to dunder methods—just as dunder methods themselves have special names.
underscore is normally used to denote that something is for private use
That's what a single leading underscore means. __<identifier>__
doesn't inherit that meaning. There are some non-magic-method identifiers using that naming scheme that are meant to be public, e.g. __file__
.
I'm not set on any particular naming scheme. I'm just sharing some ideas.
As part of a larger effort to clean up and open source the
math
module, we have removed a number of functions that would mirror arithmetic operators. In most cases, the value of these functions is limited, since the operators can (and in fact should) be used directly, e.g.However, for code that passes around these as function pointers or parameters, it might be convenient to import these from a central module rather than having to define wrappers for each of these. Python's
operator
exists for slightly different reasons, but IMO it sets a pretty clear precedent and has a clear scope. The main difference I envision compared to Python is that in Mojo we probably want a trait for each of these methods. Consequently, this could be a natural place forAbsable
(and others) to live in, instead of being a builtin and implicitly imported everywhere. This is another reason why I'm considering work on this module: declaring these traits in a single place would make it easier to compose them, and build more complex abstraction on top of them.Are there any concerns with this? I am also looking for ideas on how to test these: the tests should not rely on particular implementations of these methods (e.g. those of
Int
's).To be clear, this is not very high priority for the stdlib team, but since it's a self-contained, relatively simple, and fairly well defined module, it would be something that the community could work on, if we decide to move forward with it. In particular, seems like this might include many "good first issue" type tasks.