Basic machinery

The machinery is extremely simple. When you define a traits like @trait Fly, it literally expands to the following code:

abstract type Fly end
trait(::Type{Fly}, T::Type) = Negative{Fly}()
is_trait(::Type{Fly}) = true

As you can see, a new abstract type called Fly is automatically generated. By default, the trait function just returns an instance of Negative{Fly}. Now, when you do @assign Duck with Can{Fly},Can{Swim}, it is expanded to the following:

trait(::Type{Fly}, ::Type{<:Duck}) = Can{Fly}()
trait(::Type{Swim}, ::Type{<:Duck}) = Can{Swim}()

There are several aliases defined for the Positive parametric type e.g. Can, Has, and Is. See BinaryTraits.Prefix sub-module for the complete list of aliases. The aliases are not exported by default, and you are expected to import only the ones that you need.

Composite traits

Making composite traits is slightly more interesting. It creates a new trait by combining multiple traits together. Having a composite trait is defined as one that exhibits all of the underlying traits. Hence, @trait FlySwim with Can{Fly},Can{Swim} would be translated to the following:

abstract type FlySwim end

function trait(::Type{FlySwim}, T::Type)
    if trait(Fly,T) === Can{Fly}() && trait(Swim,T) === Can{Swim}()

is_trait(::Type{FlySwim}) = true

Turning on verbose mode

If you feel this package is a little too magical, don't worry. To make things more transparent, you can turn on verbose mode. All macro expansions are then displayed automatically.

julia> BinaryTraits.set_verbose!(true)

julia> @trait Iterable
┌ Info: Generated code
│   code =
│    quote
│        abstract type Iterable <: Any end
│        BinaryTraits.trait(::Type{Iterable}, T::Type) = begin
│                Negative{Iterable}()
│            end
│        BinaryTraits.is_trait(::Type{Iterable}) = begin
│                true
│            end
│        nothing
└    end