Closed KlausC closed 4 years ago
This is a great idea! I think doing this will solve issue #11 as well.
So we just need a new syntax. For example, if we adopt underscore to be the token for the object, then we can write:
@trait Mappable prefix Is,Not
@implement IsMappable by map(f, _)
The underscore is a good syntax for that! We could also support multiple usage of the same type in the argument type list, e.g.:
@trait Multipliable prefix Is,Not
@implement IsMultipliable by *(_, _)
Setting this to high priority because
It's a little unclear about how to support the case when there are multiple underscores. For example:
@trait PlayNice
@implement CanPlayNice by play(_, _)
struct Cat end
@assign Cat with CanPlayNice
struct Dog end
@assign Dog with CanPlayNice
Now, what makes Cat
satisfy its contracts? At this point, there are 4 combinations:
play(::Cat, ::Cat) # 1
play(::Cat, ::Dog) # 2
play(::Dog, ::Cat) # 3
play(::Dog, ::Dog) # 4
I would think that case 1,2,3 should be checked because they all involve Cat
when we call @check(Cat)
. But then we need to enumerate all possibilities. We only have Cat
and Dog
so far but what if we add more types to CanPlayNice
? Then case 2 & 3 needs to be expanded to cover the new types. So we would have to call has_method
many times for various combinations.
Alternatively, this is probably where Holy Traits pattern shines. Using the following code, the duck typed function would satisfy all possibilities. Is it cheating?
play(x, y) = play(playnicetrait(x), playnicetrait(y), x, y)
play(::CanPlayNice, ::CanPlayNice, x, y) = "great!"
play(::PlayNiceTrait, ::PlayNiceTrait, x, y) = "nah... let's go home"
It's a little unclear about how to support the case when there are multiple underscores
Yes. I have the same impression. My thoughts about it.
@implement
definition clearly defines a contract related to the CanType
-trait, not to the "assignable" types.m
underscores and n
assignable types with the same can-type-trait the amount will grow to to n^m
potential implementations. It is improbable, that in general there will be this amount of methods implemented. It is also not checkable, because we do not know n
; otherwise the contract need to be "closed" or the check results are volatile.m == 1
, but not in general. I think now, that we should "dictate" the use of a certain kind of traits (we support only Holy-traits in the @traits
macro, either). Additionally we have to establish a convention about the methods signatures when this kind of traits is used. For example, the traits objects must be the first arguments of the method as your example suggest. Or we introduce another symbol for the position of the traits-object, which gives more flexibility to the user.
For the check macro, we can restrict to m
cases, where all can-type-objects are verified and only one of the m
positions must accept the assignable type, while the other m-1
positions are unrestricted (Base.Bottom
). That means, if one of the positions accepts a Cat
we don't care if the other is a Dog
or a Mouse
or something yet unknown, given they have the CanPlayNice
trait.New syntax for the @implement
macro using an additional symbol.
@implement CanPlayNice by play(&, _, &, _)
where &
indicates the position of the can-trait; number and order of &
and corresponding _
must match.
Cat
would be done for
play(::CanPlayNice, ::Cat, ::CanPlayNice, ::Base.Bottom)
play(::CanPlayNice, ::Base.Bottom, ::CanPlayNice, ::Cat)To understand me right, 1. - 5. are only my personal brainstorming about the initial question. It is not clear, if we should go into this direction. Finally it is your decision and it possibly it complicates the design too much. Just not support multiple place holders for single can-type would be an elegant alternative.
Another idea: We let it as is currently implemented in #45 and document it properly. So the declaration
@implement CanXYZ by xyz(_, _)
is interpreted analogously to
function xyz(::T,::T) where T <:CanXYZ end
in plain Julia
: both arguments must have identical type T
, which is restricted by an abstract type.
In your example, Cat
plays nice with Cat
and Dog
plays nice with Dog
, but Cat
does not do with Dog
. (From my experience with my own dog and our daughter's cat that is very realistic :-)
I like the option that requires zero effort ☝️ 😄
It may be good to see some adoption of this package before we make it too fancy. If there's a need, we will hear more about real use cases, and then we can discuss further and get more perspective.
The type
T
which is assigned a trait determines the argument type of the first argument of the method specified in the @implement macro. That is a restriction with respect to the methods which can be requested to be implemented.Wouldn't it be a better option to allow to specify the position of this argument?
For example the methods in
Base
, which need in iterator do not all expect the iterator as the first argument (map
,reduce
,get!
, .........)