AlgebraicJulia / Catlab.jl

A framework for applied category theory in the Julia language
https://www.algebraicjulia.org
MIT License
605 stars 58 forks source link

Functor types #194

Open jpfairbanks opened 4 years ago

jpfairbanks commented 4 years ago

Why don't we create an abstract type Functor that has the API:

dom(F::Functor)::Theory
codom(F::Functor)::Theory
function (F::Functor)(x::Ob{:generator})
    ...
end
function (F::Functor)(f::Hom{:compose})
    foldl(compose, map(x->F(x), f.args))
end
function (F::Functor)(f::Hom{:generator})
    ...
end

The idea being that the functor type creator just has to specify what happens on the generators of the theory and the term constructors. Then the MonoidalFunctor <: AbstractFunctor would have the following additional method definitions:

function (F::MonoidalFunctor)(x::Ob{:otimes})
    foldl(otimes, map(F, x.args))
end
function (F::MonoidalFunctor)(f::Hom{:compose})
    foldl(compose, map(F, f.args))
end
function (F::MonoidalFunctor)(f::Hom{:otimes})
    foldl(otimes, map(F, f.args))
end

In this setting, in order to make a functor, you would just have to define a type MyFunctor<:MonoidalFunctor which supplies the action on the generators. We could supply a PresentedMonoidalFunctor which works like the current implementation by storing a dictionary mapping the generators to their images under the functor.

struct PresentedMonoidalFunctor{K,V} <: MonoidalFunctor
    g::Dict{K,V}
end
function (F::PresentedMonoidalFunctor)(x::Ob{:generator})
    F.g[x]
end
function (F::PresentedMonoidalFunctor)(f::Hom{:generator})
    F.g[x]
end

I think we could simplify a lot of this code if we store the GATExprs with the operation as a Function instead of a Symbol. The current implementation looks like:

julia> X = Ob(FreeSymmetricMonoidalCategory, :X)
julia> dump(X)
Catlab.Theories.FreeSymmetricMonoidalCategory.Ob{:generator}
  args: Array{Symbol}((1,))
    1: Symbol X
  type_args: Array{GATExpr}((0,))

Instead of storing head(ex) == :otimes we could store the function otimes, this would allow a single abstract functor type with the idea that the codomain category will support all the term constructors of the domain category so that you can implement arbitrary GAT functors:

function (F::Functor)(x)
    foldl(head(x), map(F, x.args))
end

#you still need a base case for generators
Generator = Union{Ob{:generator}, Hom{:generator}}
function (F::Functor)(x::Generator)
    F.g[x]
end
JeffreySarnoff commented 4 years ago

Sounds good to me.

olynch commented 4 years ago

In the @theory macro, could we generate an abstract type representing maps between instances of the theory that only made you define the map on generators?

Alternatively, we could write a @theory_map macro that took in a theory and a name for the theory map (i.e. Category and AbstractFunctor) and made AbstractFunctor into an abstract type with implementations for the operations of the theory.

jpfairbanks commented 4 years ago

That would be really cool!

olynch commented 4 years ago

Well, don't get too excited; I'm working out Functors and MonoidalFunctors now, and we'll see how that goes before I attempt that.

epatters commented 4 years ago

Totally agreed about storing Function instead of Symbol for term constructors like compose and id. This should allow us to eliminate complicated reflection machinery in invoke_term and also get a performance boost by going through Julia's standard dispatch mechanism.