Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Another macro for constructing ACSets! #354

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

olynch
Copy link
Member

@olynch olynch commented Dec 11, 2020

I was inspired to write this while reading Sophie Libkind's post; there was a section where she created an acset and I thought "there must be a better way."

Example usage:

@present_acset DecGraph{Symbol}, with_names(E,dec) begin
  (w,x,y,z) :: V
  (a,b,c,d) :: E(src=[w,x,y,z],tgt=[x,y,z,w])
end

produces the same result as

@acset DecGraph{Symbol} begin
  V = 4
  E = 4

  src = [1,2,3,4]
  tgt = [2,3,4,1]

  dec = [:a,:b,:c,:d]
end

Each "with_names" argument allows you to capture the symbols used in the macro as attributes in the final ACSet.

@olynch
Copy link
Member Author

olynch commented Dec 11, 2020

Theoretically, we could even add type-checking to this!

@olynch olynch requested a review from epatters December 11, 2020 21:30
@jpfairbanks
Copy link
Member

Whoa that is awesome. While variable names are a pain in the neck in the implementation, they are super helpful for humans writing the input. with_names isn't clear to me, maybe names(E) = dec which says that the names of elements in E are going to be stored in dec.?

@olynch
Copy link
Member Author

olynch commented Dec 11, 2020

Yeah, I'm totally not attached to with_names, it was just the first thing I thought of. I'm also not attached to @present_acset; I would definitely be up for changing that to something else if desired.

Also, I'm thinking about adding a feature to this where you can use it to add things to an existing acset, and I kind of want to have

@present_acset g2 <: g1 begin
  ...
end

Is it too weird to have that, when we normally use <: for types?

@jpfairbanks
Copy link
Member

What if we used the unicode \subset? it parses as a binary operator.

@epatters
Copy link
Member

epatters commented Dec 11, 2020

On a roll lately with the macros! Thoughts:

  1. Instead of @present_acset, how about @named_acset?

  2. I also think that using <: for values instead of types is weird. Actually, we don't need to put the new variable name in the macro call, right? So we could just have

g2 = @named_acset g1 begin
  ...
end
  1. Yeah, the syntax with_names feels arbitrary and hard to remember. Not completely sold on this either, but what about:
@named_acset DecGraph{Symbol} begin
  (w,x,y,z) :: V
  (a,b,c,d) :: E(src=[w,x,y,z], tgt=[x,y,z,w], dec=E)
end

@jpfairbanks
Copy link
Member

If we are thinking about adding incrementally to an existing ACSet X (defining maps out of X) we should also think about creating sub ACSets (defining maps into X) and use similar syntax for those operations.

@olynch
Copy link
Member Author

olynch commented Dec 12, 2020

I'm not sure exactly how that would work, can you elaborate with an example?

If I'm parsing what you are saying correctly, then I think it might only work in cases where the "naming attribute" is uniquely indexed. But that is an important special case!

@olynch
Copy link
Member Author

olynch commented Dec 12, 2020

  1. @named_acset doesn't really get me going, but I don't have that strong of an opinion; if we don't come up with anything else I'd be happy to call it @named_acset.

  2. There needs to be something syntactically that distinguishes extending an ACSet from creating a new one.

  3. Ideally, you would just define which attribute is a "naming attribute" once, instead of having to specify it each time you declared a new variable.

Also, question, should I replace the tuple syntax (w,x,y,z) with array syntax [w,x,y,z]?

Naming things is so hard!!

@jpfairbanks
Copy link
Member

Name of the macro

I think integrating this with the features of the main @acset macro is good. People will want to use the named version more often than the numbered one so I think this makes sense to use @acset for this.

Taking Evan's example above:

@acset DecGraph{Symbol} begin
  (w,x,y,z) :: V
  (a,b,c,d) :: E(src=[w,x,y,z], tgt=[x,y,z,w], dec=E)
end

If you made the left hand sides Integer ranges, then we could subsume the number based acset macro

@named_acset DecGraph{Symbol} begin
  1:4 :: V
  1:4 :: E(src=[1,2,3,4], tgt=[2,3,4,1], dec=[:a,:b,:c,:d])
end

Then you could default to ranges that start at 1

@named_acset DecGraph{Symbol} begin
  4 :: V
  4 :: E(src=[1,2,3,4], tgt=[2,3,4,1], dec=[:a,:b,:c,:d])
end

Distinguishing extension from making a new ACSet

I think taking

g2 = @named_acset g1 begin
  ...
end

and maybe

@acset g1 \to g2 begin
  ...
end

as a way of specifying g2 and a map from g1 to g2 together would be a great feature. When we say that we "add data to an existing acset X, we are really saying that we construct a new acset Y and a map from X to Y. When we say that X is a subacset of Y, then we are really specifying X and map from X into Y. Since we think of inheritance of schemas as a functor from the parent schema into the child schema, which induces a pullback functor of the corresponding acsets, I think we should use that approach as a unified doctrine of inheritance. So when an acset inherits from another (by extension) that is a hom from the base acset to the extended acset.

One of the lessons of categorical algebra is that everything gets easier if you think about computing homs instead of computing objects. I think we should apply it here and make these inheritance/extensions/inclusions work by constructing homs between acsets.

@epatters
Copy link
Member

epatters commented Dec 14, 2020

Totally agree with @jpfairbanks about the naming issue: we should unify the @acset macro to support a range of useful syntaxes, just like the @relation macro now has several syntactic variants producing different flavors of UWDs.

The point about sub-ACSets is also good and would partly address #349.

@olynch
Copy link
Member Author

olynch commented Dec 14, 2020

One thing is that it doesn't actually make sense to declare an arbitrary range 3:7 :: V if we haven't declared vertices 1 and 2 yet. So I think we should only allow 4 :: V. But that is slightly awkward, if you have any different syntax I'd be happy to hear it.

But I'm glad that this solves the naming issue; we just call it all @acset, and forget about @named_acset.

Still there is a problem about the need to syntactically delineate @acset Graph begin and @acset g begin. What about adding a parameter, so we have @acset Graph, from=g begin. This could even support inheriting from multiple acsets with @acset Graph, from=g,h begin.

Also, what if we use some sigil to indicate which attribute is the naming attribute? Like

@acset DecGraph{Symbol} begin
  (w,x,y,z) :: V
  (a,b,c,d) :: E(src=[w,x,y,z], tgt=[x,y,z,w], !dec)
end

Also, I think that the row-wise assignment that I pushed off in the last macro would be good here

@acset DecGraph{Symbol} begin
  (w,x,y,z) :: V
  @define E(src,tgt,!dec) begin
    a = w,x
    b = x,y
    c = y,z
    d = z,w
  end
end

@olynch
Copy link
Member Author

olynch commented Dec 14, 2020

Hell, if we wanted to be really fancy, we could have something like

@acset DecGraph{Symbol} begin
  (w,x,y,z) :: V
  @define E(src,tgt,!dec), formula = src -> tgt begin
    a = w -> x
    b = x -> y
    c = y -> z
    d = z -> w
  end
end

I have no idea how easy it would be to implement, but if we ever really need a flex, we can refer back to this comment and try to get it working. Though, now that I think about it, we might be able to use the machinery from MLStyle.jl, with @match, so we would have something like

@match e begin
  Expr(:call, :(->), src, tgt) => (src,tgt)
  _ => error()
end

@olynch
Copy link
Member Author

olynch commented Dec 14, 2020

In relation to all this, I had a thought when I was walking today. It is useful to have persistent names for elements of an acset for defining functions in/out of them. However, logically these names aren't really attributes; they should have no mathematical impact. It would also be annoying to have to define new ACSet types with uniquely indexed names every time you wanted to define maps using human-friendly syntax (i.e., not just integers).

My proposed solution for this is that we implement a Tables.jl-compatible typed dataframe that can be indexed by numbers and also symbols. This could be as simple as a StructVector + Dict{Symbol,Int}. Then any acset schema can be used with or without names for its elements.

Then @acset would automatically work with this, and would additionally set attributes based on the names given in the code if also specified. This would mean that you could use the same names when extending an acset as when you originally defined it. And of course, it would be easy to write a function that strips away all the names, changing the type to a Tables.jl implementation without symbol-indexing.

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants