GithubHelp home page GithubHelp logo

tk3369 / binarytraits.jl Goto Github PK

View Code? Open in Web Editor NEW
50.0 50.0 3.0 1.07 MB

Can do or not? It's easy. See https://tk3369.github.io/BinaryTraits.jl/dev/

License: MIT License

Julia 99.85% Dockerfile 0.15%
design-patterns interface julia-language julialang traits

binarytraits.jl's People

Contributors

klausc avatar mbaz avatar tk3369 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

binarytraits.jl's Issues

Implement return type check

We still haven't incorporated the return type of contracts into the check function. Ideally, this should be an optional thing because the compiler may not reliably infer the return type of a function. Perhaps the default is false (i.e. do not check return type) because it's not 100% reliable but the user can turn that on when using the check function.

Interface Checking Broken on 1.10.0

MWE

using BinaryTraits
using BinaryTraits: Prefix

julia> @trait Foo

julia> @implement Prefix.Is{Foo} by f(_, x)

julia> struct Bar end

julia> f(::Bar, x) = 1

julia> @assign Bar with Prefix.Is{Foo}

julia> @check Bar
ERROR: Tuple field type cannot be Union{}

The issue seems to be covered by this issue on the Julia repo. If that problem is resolved such that we can use Tuple{Union{}} again, I suspect this will go back to working

Interface contracts should propagate to subtypes

Consider this example:

@trait Fly
@implement CanFly by liftoff()

abstract type Bird end
struct Duck <: Bird end
struct Chicken <: Bird end

@assign Bird with Fly

Since I assigned the abstract type Bird with the Fly trait and the Fly trait requires an interface contract liftoff, I expect both Duck and Chicken to satisfy that contract. However:

julia> check(Duck)
✅ Duck has no interface contract requirements.

The interface check should traverse super-type tree and check all required contracts.

RFC: Allow multiple traits in one interface

It should be possible to define interfaces, which require more than one interface type.
Example with proposal for syntax:

@trait ProblemDescription prefix Is,IsNot
@trait Solve
@implement Tuple{IsProblemDescription,CanSolve} by solve(_2, _1, general_parameters)

Global state `prefix_map` needs to be revisited

First, it's kinda weird that we're storing client data in BinaryTraits module. Second, I just found another problem due to this design:

The global state is being re-initialized and the data gets wiped away. The case come up with I have a module X that uses BinaryTraits. In module X, I defined a trait. When using X is finished, BinaryTraits.prefix_map is empty again, where it should have a map for the trait that I defined in module X.

I think it would be cleaner to put this map in the client module's namespace - perhaps define a function that takes the trait symbol and map that to the prefixes.

For this reasons, I'm retracting PR #36

Unable to check interface from outside a module

If I define a module and then try to verify interfaces from outside of the module, it gets confused:

julia> module Foo
           using BinaryTraits
           using BinaryTraits.Prefix: Is
           @trait Cute
           @implement Is{Cute} by awesome(_)
           struct Wat end
           @assign Wat with Is{Cute}
       end

julia> @check Foo.Wat
✅ Main.Foo.Wat has no interface contract requirements.

The workaround is to run the check inside the module, or expose a method to do that:

julia> module Bar
           using BinaryTraits
           using BinaryTraits.Prefix: Is
           @trait Cute
           @implement Is{Cute} by awesome(_)
           struct Wat end
           @assign Wat with Is{Cute}
           testme() = @check Wat
       end

julia> Bar.testme()
┌ Warning: Missing implementation
│   contract = BinaryTrait{Main.Bar.Cute}: BinaryTraits.Prefix.Positive{Main.Bar.Cute} ⇢ awesome(🔹)
└ @ BinaryTraits ~/.julia/dev/BinaryTraits/src/interface.jl:62
❌ Main.Bar.Wat is missing these implementations:
1. BinaryTrait{Main.Bar.Cute}: BinaryTraits.Prefix.Positive{Main.Bar.Cute} ⇢ awesome(🔹) (Missing implementation)

RFC: allow to assign parameterized types with traits

It would be cool, if I could do:

@trait Sparse prefix Is,Not
@assign Adjoint{T,SparseVector{T}} where T with Sparse

Currently I have to use this work-around, because the @assign macro expects a Symbol as a first argument.

julia> @trait Sparse prefix Is,Not

julia> const AVS = Adjoint{<:Any, SparseVector}
Adjoint{var"#s54",SparseVector} where var"#s54"

julia> @assign AVS with Sparse
julia> @implement IsSparse by nnz()
julia> nnz(a::Adjoint{<:Any, SparseVector}) = nnz(a.data)
nnz (generic function with 2 methods)

julia> check(Adjoint{Float64, SparseVector})
✅ Adjoint{Float64,SparseVector} has implemented:
1. SparseTrait: IsSparse  nnz(::<Type>)::Any

Interface specification v2

The current implementation associates the trait type <T>Trait with a set of contracts. It seems that it makes more sense to associate the can-trait i.e. Can<T> to the contracts instead.

Mandated return value for a contract

Some interfaces requires the implementer to define a function that returns true so that it knows if a type has implemented the interface at runtime.

Examples:

  • Tables.istable
  • IteratorInterfaceExtensions.isiterable

Technically, this is not necessary anymore if these packages adopt BinaryTraits because they can just call the trait function e.g. trait(Table,x) and it would return Is{Table}() for a properly implemented type.

I do not expect, however, that everyone will adopt BinaryTraits. Yet, an implementer may still want to use BinaryTraits' function to verify correct implementation of the interface. It can be achieved if we have a "BinaryTraitsLibrary" that defines a bunch of well-known traits and the respective interface contracts. Now, one of the contracts for Tables.jl will be "hey, you must extend my istable function and return true".

For that reason, we can design syntax such that a contract must return a true value.

BUG: Antipattern used in implementation

At several spots in the source code I found this pattern:

x = get!(dict, key, Constructor())

That will create an instance of Constructor every time the line is executed (while needed only once for each key). It should be replaced by

x = get!(dict, key) do; Constructor() end

`@check` failed with `ERROR: LoadError: type UnionAll has no field parameters`

Hi,

I encountered the error when running the code below:

import Distributions: rand, logpdf

"""
Generalized distributions that support sampling (and evaluating the probability of) 
structured data. 
"""
abstract type GDistr end

@trait GDistr prefix Is, IsNot
@implement Is{GDistr} by rand(rng::AbstractRNG, _)
@implement Is{GDistr} by logpdf(_, x)

@assign Distribution with Is{GDistr}
@check Distribution

Is this the expected behavior? Or am I doing something wrong? (My Julia version is 1.7)

The stack trace:

ERROR: LoadError: type UnionAll has no field parameters
Stacktrace:
 [1] getproperty
   @ ./Base.jl:37 [inlined]
 [2] check_method(m::Method, sig::Type{Tuple{typeof(Distributions.logpdf), Distributions.Distribution, Union{}}}, kwnames::Tuple{})
   @ BinaryTraits ~/.julia/packages/BinaryTraits/58m13/src/interface.jl:256
 [3] has_method(f::Any, t::Any, kwnames::Tuple{})
   @ BinaryTraits ~/.julia/packages/BinaryTraits/58m13/src/interface.jl:244
 [4] check(m::Module, T::UnionAll)
   @ BinaryTraits ~/.julia/packages/BinaryTraits/58m13/src/interface.jl:56
 [5] top-level scope
   @ ~/.julia/dev/SEDL/src/distributions_utils.jl:17

@implement does not support keyword arguments

Keyword arguments are not supported

This is actually a limitation with Julia itself because keyword arguments are not considered for dispatch. I also wonder how useful it would be… Can you share a use case for this if you happen to have one?

Cross-module interface implementation

Problem statement

Up till now, the trait/assignment/interface/checker functionalities work fine if everything is defined in the same module. It becomes tricky when the trait is defined in one module but needs to be implemented in another. There are a couple of issues:

  1. Namespace - what if the same trait is defined from different third-party modules and they mean different things?

  2. Currently, the traits/prefix/interface maps are stored in BinaryTraits module and it is

Proposed solution

In order to support a module implementing an interface defined from another module, I would like to propose a new syntax. The following hypothetical example illustrate how we could declare our data type for conforming to the Tables.jl's RowTable interface from this fork.

@assign AwesomeTable with  Tables.RowTable

Having implemented PR #38 , it is now possible to implement this syntax change. The macro @assign can just parse the module name (caution: it could have multiple tokens) and then look up the trait from the client module's traits map. In this case, the RowTable trait would be stored in the Tables module's map. When we check the interface contracts, we would have to look up the contracts as defined in Tables module as well.

@KlausC - let me know what you think...

@implement requires argument types which can be expressed as Symbols

The specified argument type needs to be simple, for example no type parameter allowed

Right, I realized the missing feature as well. Currently, it needs to be a simple “symbol” for the parser to work. I guess it’s less convenient but you can define a constant and then use it in the interface. See the CartesianIndexing trait example.

Handling the prefixes.

This is a pretty minor issue but prefixes are constants so they print out as Positive or Negative. There's clearly an advantage to having a constant instead needlessly creating a bunch of extra types, like unique binary prefix types. The only idea I really have is to create a specialized show method inside the @trait macro. Something like...

function Base.show(io::IO, ::MIME"text/plain", ::Negative{T}) where {T<:NewlyCreatedTrait}
    print(io, "Not{$T}")
end

I don't see any other way of doing this than defining show inside the macro because that's the only place you'll be able to track down the actually prefix syntax that is used.

It's also not a terribly big deal, unless for some reason people are printing a prefix in the REPL, so unless there's a more ideal solution available this might not even be worth addressing.

Contradictory Traits Break When Applied to the Type Hierarchy

I would like to apply a default trait to an abstract type, and then special case some subtypes with the negated trait. Take for example below:

abstract type Bar end
struct Foo <: Bar end

@trait A_Foo prefix Is,Not
@implement Is{A_Foo} by getfoo(_)
@assign Bar with Not{A_Foo}
@assign Foo with Is{A_Foo}

@traitfn getfoo(::Not{A_Foo}) = "Not a Foo!"

# This passes, but seems like it shouldn't
@check Foo

Foo passes the check, but getfoo(Foo()) fails because I haven't implemented getfoo(::Foo) yet in accordance with the Is{A_Foo} interface. This behavior seems nuanced since it requires associating only one of Is{A_Foo} or Not{A_Foo} to each type at a time, so it may contradict BinaryTraits's implementation.

Alternatively, if there's a better way/a design pattern to achieve this behavior (default trait applied to abstract type, special cased implementation of the negated trait to subtypes) I am open implementing that instead

Easy syntax for holy trait dispatch function

It seems to be a hassle to define the "entrypoint" function for every function that supports traits.
For example:

function plot(device::T, x::Vector{Float64}, y::Vector{Float64}; color = :black, linewidth = 1) where T
    plot(trait(Plottable,T), device, x, y; color, linewidth)
end

It would be nice to have syntax like:

@holy Plottable plot(device, x::Vector{Float64}, y::Vector{Float64}; color = :black, linewidth = 1)

Note: I can't think of a good verb yet (hence @holy 😄 ).

Support interface specification

High level requirements

  • specifying interface definitions (aka contracts) for traits
  • checker function to see if a data type fully satisfies all required contracts
  • easy-to-read syntax

@implement requires explicit argument types

The argument requires to specify an argument type for each argument - could default to Any

Yes, but I am unsure if Any is the right default. When I tried to use it with the AbstractArray interface, which uses duck typing in the interface, I figured that I need to use Base.Bottom instead. See the LinearIndexing trait example. If we want to impose a default then I think Bottom is more appropriate because otherwise the implementer must define the function that accepts explicitly Any.

Feature Request: Allow Traits to Subtype Arbitrary Type

It would be helpful if we could have traits subtype some type we define, rather than Any. This allows us to filter traits by supertype when generating documentation.

For example, say I want to make an Animal interface, and then I have Fish and Bird traits as well each with their own interface that is independent of Animal. I add a docstring to each of these Animal, Fish, Bird traits to document the interface they must satisfy.

Now I want to make a Habitat interface, and within that I have Water and Tree traits which each include their own interfaces. Again, I document the interface in the trait itself.

I would like to generate a Documenter.jl page which includes all of the Animal interfaces, and another page which includes all of the Habitat interfaces with @autodocs. Currently, we can do this by manually including the docstrings for each trait or restricting trait implementations to particular files, but it would be helpful if I could filter the trait types according to the Julia type hierarchy to generate my interface documentation.

In the end, I'd like to generate an Animals interface page via

@autodocs
Module = [Foo]
Filter = t -> typeof(t) === DataType && t <: Foo.AbstractAnimal

where AbstractAnimal <: Any is an abstract type I create to make sure that {Animal, Fish, Bird} <: Abstract Animal are all independent types in the type tree

Relevant link to the Documenter.jl docs

Make as/prefix/with clauses optional

Here's a sample usage of the @trait macro:

@trait FlySwim as Ability prefix Can,Cannot with Fly,Swim

The issue is that you cannot specify the prefix-clause without the as-clause, and you cannot specify the with-clause without the prefix-clause. It would be nice if the as-clause, prefix-clause, and with-clause are all optional.

Documentation

The README is growing pretty fast so it would be better to move to docs. Also, we can publish doc strings and execute doclets for developer documentation.

Function signature in @implement macro fixes meaning of first argument

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!, .........)

Simplify trait types with a parametric design?

From Janis Erdmanis via Slack:

The API is rather clear and seems I could find it useful for enforcing interface. However it also feels a bit verbose. Looking at the under the hood section I wonder wouldn't it be possible to have an API:

struct Lack{T} end
struct Has{T} end
abstract type FlyTrait <: Ability end
FlyTrait(x) = Lack{FlyTrait}

And then the methods would have a form:

tickle(::Has{FlyTrait}, ::Has{SwimTrait}, x) = "Flying high and diving deep"

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.