-
Notifications
You must be signed in to change notification settings - Fork 112
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
Add facilities to streamline GAP wrappers for GAP properties/attributes; reduce overhead #128
Comments
After some discussion on the Julia Slack, I learned about the
Bonus points if someone can produce a macro that makes it easier to do the above. Something which can be used like this: @gap_wrapper isabelian(G::GAPGroup) = GAP.Globals.IsAbelian(G.X)::Bool ... and which takes all |
The following is a working implementation of a macro as described above; I'll integrate this into Oscar (or GAP.jl?) later (don't want to generate too many conflicts with @GDeFranceschi work for now). using GAP
using MacroTools
macro gapwrap(ex)
# split the method definition
def_dict = try
MacroTools.splitdef(ex)
catch
error("@gapwrap must be applied to a method definition")
end
# take the body of the function
body = def_dict[:body]
# find, record and substitute all occurrences of GAP.Global.*
symdict = IdDict{Symbol,Symbol}()
body = MacroTools.postwalk(body) do x
MacroTools.@capture(x, GAP.Globals.sym_) || return x
new_sym = get!(() -> gensym(sym), symdict, sym)
return Expr(:$, new_sym)
end
# now quote the old body, and prepend a list of assignments of
# this form:
# ##XYZ##123 = GAP.Globals.XYZ
def_dict[:body] = Expr(
:block,
# first the list of initializations ...
(:(local $v = GAP.Globals.$k) for (k, v) in symdict)...,
# ... then the quoted original-with-substitutions body
Meta.quot(body),
)
# assemble the method definition again
ex = MacroTools.combinedef(def_dict)
ex2 = :(@generated $ex)
# we must prevent Julia from applying gensym to all locals, as these
# substitutions do not get applied to the quoted part of the new body,
# leading to trouble if the wrapped function has arguments (as the
# argument names will be replaced, but not their uses in the quoted part
# of the body)
return esc(ex2)
end
@gapwrap isevenint(x) = GAP.Globals.IsEvenInt(x)::Bool |
Concerning the question where to put the code (Oscar or GAP), I think that |
The |
Yes, but all the examples (use cases) in earlier comments involve The macro The aspect of uniformly/automatically modeling GAP's attribute getter/tester/setter mechanism (the original idea of this issue?) is not the job of the above |
Thinking again about the question where to put the macro(s) described in this issue, I am still undecided. |
We discussed this and agreed that we'll put it into GAP.jl, even if we don't really need it there. |
Use the macros `@gapattribute` and `@gapwrap` in order to get access to GAP's "tester" and "setter" functions for its attributes. This had been suggested in issue #128. The code requires the changes from the pull request oscar-system/GAP.jl/pull/647. The branch has the same name as that of the current pull request. (There are more places where the macros can be applied. First we should agree on the setup, see below.) - The names of the tester/setter functions on the Julia side should perhaps be changed: Would `has_derived_subgroup` look better than `hasderived_subgroup`? (This is something to be changed in GAP.jl.) - Some of the functions in `Oscar/src/Groups` (`conjugacy_classes`, `conjugacy_classes_subgroups`, `conjugacy_classes_maximal_subgroups`) cannot be handled by the current `@gapattribute`, because they involve several calls to some function in `GAP.Globals`. The point is that the names of the tester and setter are "guessed" from the body of the given (getter) function. If there is just one call to a function in `GAP.Globals` then this is safe, otherwise we have to find some heuristic that is easy to document. - In a few cases (`exponent`), I have added individual `import` statements because the current `@gapattribute` does not strip module names from the beginning of the function name; we could change the macro in order to avoid these `import` statements. - In order to keep the docstrings of the functions handled by `@gapattribute`, I did not see a better way than to move the code above the docstring. - Another point for the documentation is how to document the testers and setters. We could show three-line descriptions of the kind ``` isperfect(G::Group) hasisperfect(G::Group) setisperfect(G::Group, val) ``` in all relevant places, instead of just the first of these lines. Or would this become too long? (I have extended the `export` lists by the testers and setters. Is this o.k.?) - I have changed the behaviour of the functions only in one place. Up to now, `trivial_subgroup` constructed just a group on the Julia side, without calling GAP's `GAP.Globals.TrivialSubgroup`, but now the attribute on the GAP side is called, due to `@gapattribute`. The side-effect is that now the return value of `trivial_subgroup` has an empty list of generators, whereas this list contained the identity element before. This change seems to be no problem for the tests, but perhaps it has to be documented that the result of `gens` may be empty. The conceptual question is whether we really want to change `trivial_subgroup` such that the corresponding GAP function gets called. We can take the viewpoint that the Julia side should better avoid work on the GAP side where possible (one can create a trivial subgroup in Oscar without "help from GAP"), or we can argue that the GAP side should better be kept informed about knowledge that has been computed on the Julia side. Note that in the cases of boolean values, the stored GAP attributes really serve as caches; but already for a group order stored in GAP, a conversion may be necessary before the value can be used on the Julia side. One "border case" is `ispgroup`: Its return value is a tuple not just the boolean value that is stored on the GAP side. Any access from Oscar's `ispgroup` creates a new Julia object. - The following two inconsistencies should be dealt with in other pull requests: `center` and `ischaracteristic` are known in (and imported from) Hecke, but Oscar.Groups defines `centre` and `ischaracteristic_subgroup`.
We've dealt with this quite some time ago. |
We have a lot of GAP wrapper functions that looks like this:
or this:
They all follow roughly this pattern:
GAP.Globals.FOOBAR
But both GAP functions called above are GAP attributes (the first one is a special case, a GAP property, which is an attribute that can only take on the values true or false). For GAP attributes, one really also should expose their corresponding testers (
HasIsPerfectGroup
->hasisperfect
orknowsisperfect
or so; andHasComplementSystem
->hascomplement_system
orhas_complement_system
or whatever), and possibly also setters (SetIsPerfect(group, bool)
can be useful when building new group objects about which extra information is known, as isSetComplementSystem
)). Right now, that means one should actually add three functions for each (and of course keep consistent naming in mind); e.g.Moreover, in GAP one can actually query any attribute for its tester or setter:
Tester(IsPerfectGroup)
returnsHasIsPerfectGroup
. We could offer a similar feature, e.g.tester(isperfect)
would returnhasisperfect
.And then there is another thing: using
GAP.Globals.FOOBAR
currently has quite some overhead (see oscar-system/GAP.jl#485 for some more information). While I hope to reduce this overhead in general, it will never be zero. We could mitigate this by fetching this value once and storing it in a Julia const var, like so:(This has one drawback: in GAP one can change almost everything; a user could with some effort replace
IsPerfectGroup
by something else, and then our "cache" wouldn't refer to that. However, this is a very nasty hack and I'd be happy to explicitly not support it; no GAP code or package I know does such a thing (well, some do something similar to a few functions, but nothing I am aware of does it to properties or attributes like that).Anyway:
Implementing all this in a consistent way is somewhat tedious. Also, if decide to change some naming conventions, or add/remove/improve the caching method I sketched, we'd have to do it lots of places. So I think it would make sense to introduce some facilities that help doing this, and in a consistent way. This could e.g. be a macro (or a set of macros) which in their simples form, might take the name of a GAP function, the desired Julia name and information about the arguments and their types. Something like this pseudo code
which could then emit code for the getter, tester and setter. A more powerful version of the macro could also let you specify a pre- and post-condition.
Another idea which is perhaps a bit more advanced but also more difficult to implement, would be to write a macro which takes a whole function as argument, which contains exactly one instances of
GAP.Globals.FOOBAR
; then the macro could extract everything it needs from that. I.e. you'd writewhich might get turned into something like this by the macro:
That setter is of course not great (except for properties, I guess); but my idea would that one then could easily add a custom "better" setter in terms of that, by just adding this after the
@gapwrap function ... end
bit:The text was updated successfully, but these errors were encountered: