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

Generate default keyword arguments in codegen #18

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

domluna
Copy link

@domluna domluna commented Sep 4, 2021

fixes #17

@Roger-luo
Copy link
Owner

thanks for the PR! actually I just realize we cannot change this convention for functions, this is because the JLKwField and JLField are used specifically for the structs instead of functions at the moment, e.g as a result of this convention the struct codegen will treat them as "field"s when they appear in a struct body.

https://github.com/Roger-luo/Expronicon.jl/blob/master/src/codegen.jl#L392

the function keyword should be specified as Expr(:kw, a, b), I'm not sure if we should have a more unified type for the syntax name = <value>, any idea?

@domluna
Copy link
Author

domluna commented Sep 5, 2021

I'm not sure, maybe have another arg you can pass in to denote the context is a function or sturct?

@domluna
Copy link
Author

domluna commented Sep 5, 2021

I added from_function kwarg for JLKwField codegen. It looks like it works for my use case. I guess another way could be to add new structs, like JLFnArg and JLFnKwarg which are like the Field versions but codegen slightly differently?

@Roger-luo
Copy link
Owner

Actually I'd like to learn your use case first, what's your use case of using JLKwField?

Part of the reason why I didn't use a type for function kwargs was because it's Expr is quite simple (unlike struct field that can have line number and docstring)

@domluna
Copy link
Author

domluna commented Sep 6, 2021

I'm using it to generate GraphQL types/functions from a schema - https://github.com/domluna/GraphQLGen.jl.

I'm rewriting some portions to use Expronicon instead of pure exprs since it's easier to manage things that way, and to parse things for testing. https://github.com/domluna/GraphQLGen.jl/blob/docstrings/src/jltype.jl#L234-L249

The kwarg generation is used to create functions

julia> str = """
       schema {
         query: Query
       }

       type Query {
          "this returns some books"
          books(ids: [ID!]!): Book!
          authorBooks(authorName: String!, genre: Genre, limit: Float = 10): [Book!]
       }
       """
"schema {\n  query: Query\n}\n\ntype Query {\n   \"this returns some books\"\n   books(ids: [ID!]!): Book!\n   authorBooks(authorName: String!, genre: Genre, limit: Float = 10): [Book!]\n}\n"

julia> types, functions = GraphQLGen.tojl(GraphQLGen.parse(str));
┌ Info:
│   args =
│    1-element Vector{Expronicon.JLKwField}:
│     ids::Vector{String}
│   kwargs = Expronicon.JLKwField[]
│   funcsigs =
│    1-element Vector{NamedTuple{(:name, :typ, :default), Tuple{String, String, String}}}:
│     (name = "ids", typ = "[ID!]!", default = "")
│   jlf =
│    function books(ids::Vector{String})
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│        q = s -> *("query Books(ids: [ID!]!) {
│    books(ids: $ids) {
│    ", s, "}
│    }")  #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:215 =#
│        query = q(f.query)
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:216 =#
│        variables = Dict("ids" => ids)
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
│        filter!(v -> !isnothing(v[2])  #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#, variables)
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:219 =#
│        return :($(Expr(:parameters, :query, :variables)))
│    end
└   map((kw->begin
            #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:223 =#
            kw.default
        end), kwargs) = Any[]
┌ Info:
│   args =
│    1-element Vector{Expronicon.JLKwField}:
│     authorName::String
│   kwargs =
│    2-element Vector{Expronicon.JLKwField}:
│     genre::Union{Genre, Missing, Nothing}
│     limit::Union{Float64, Missing, Nothing}
│   funcsigs =
│    3-element Vector{NamedTuple{(:name, :typ, :default), Tuple{String, String, String}}}:
│     (name = "authorName", typ = "String!", default = "")
│     (name = "genre", typ = "Genre", default = "")
│     (name = "limit", typ = "Float", default = "10")
│   jlf =
│    function authorBooks(authorName::String; genre::Union{Genre, Missing, Nothing}, limit::Union{Float64, Missing, Nothing})
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│        q = s -> *("query AuthorBooks(authorName: String!, genre: Genre, limit: Float = 10) {
│    authorBooks(authorName: $authorName, genre: $genre, limit: $limit) {
│    ", s, "}
│    }")  #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:215 =#
│        query = q(f.query)
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:216 =#
│        variables = Dict("authorName" => authorName, "genre" => genre, "limit" => limit)
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
│        filter!(v -> !isnothing(v[2])  #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#, variables)
│        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:219 =#
│        return :($(Expr(:parameters, :query, :variables)))
│    end
│   map((kw->begin
            #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:223 =#
            kw.default
        end), kwargs) =
│    2-element Vector{Any}:
│       :nothing
└     10

julia> functions[end]
quote
    #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:226 =#
    struct authorBooks
        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:227 =#
        query::String
    end
    #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:230 =#
    function authorBooks(authorName::String; genre::Union{Genre, Missing, Nothing}, limit::Union{Float64, Missing, Nothing})
        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
        q = (s->begin
                    #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
                    "query AuthorBooks(authorName: String!, genre: Genre, limit: Float = 10) {\nauthorBooks(authorName: \$authorName, genre: \$genre, limit: \$limit) {\n" * s * "}\n}"
                end)
        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:215 =#
        query = q(f.query)
        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:216 =#
        variables = Dict("authorName" => authorName, "genre" => genre, "limit" => limit)
        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
        filter!((v->begin
                    #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
                    !(isnothing(v[2]))
                end), variables)
        #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:219 =#
        return (; query, variables)
    end
end

This part here

function authorBooks(authorName::String; genre::Union{Genre, Missing, Nothing}, limit::Union{Float64, Missing, Nothing})

should generate the default values and with patch it does.

@Roger-luo
Copy link
Owner

Ah I see why now, function kwargs could have type annotations, this would be a case when a struct for it comes in convenience, what about implementing a JLKwarg then?

@domluna
Copy link
Author

domluna commented Sep 7, 2021

Would we need JLArg too then? or would be reuse JLField for that?

@Roger-luo
Copy link
Owner

Umm now after more thinking on this I believe the right way is to actually have types that represent certain syntax such as arg-like objects that can be used as fields or function arguments and kwarg like objects that can be used as kw fields or kwargs.

I also have something similar in Comonicon. I think I should have a closer look on an implementation but I got quite occupied recently (so sorry for this late reply) I think I'd like to work on this early next month after a paper I'm working on is out. Meanwhile would it be ok for now to use the Expr(:kw, a, b) as a workaround for now?

@domluna
Copy link
Author

domluna commented Sep 15, 2021

Meanwhile would it be ok for now to use the Expr(:kw, a, b) as a workaround for now?

what do you mean?

@Roger-luo
Copy link
Owner

Roger-luo commented Sep 16, 2021

I mean for now you can use

julia> fn = JLFunction(;name=:foo, kwargs=[Expr(:kw, :(name::Int), 1)])
function foo(; name::Int = 1)
end

julia> codegen_ast(fn)
:(function foo(; name::Int = 1)
  end)

julia> codegen_ast(fn)|>eval
foo (generic function with 1 method)

for now, this interface should always work.

also note, the function name should be a Symbol instead of String, I should have error on this

We just need to canonicalize the internal representation on these syntax types.

@domluna
Copy link
Author

domluna commented Sep 16, 2021

oh ic, yeah that should be a sufficient workaround

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

Successfully merging this pull request may close these issues.

function keyword arguments default values are not generated
2 participants