Expronicon.jl icon indicating copy to clipboard operation
Expronicon.jl copied to clipboard

Generate default keyword arguments in codegen

Open domluna opened this issue 3 years ago • 11 comments

fixes #17

domluna avatar Sep 04 '21 21:09 domluna

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?

Roger-luo avatar Sep 04 '21 23:09 Roger-luo

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

domluna avatar Sep 05 '21 01:09 domluna

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?

domluna avatar Sep 05 '21 16:09 domluna

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)

Roger-luo avatar Sep 06 '21 17:09 Roger-luo

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.

domluna avatar Sep 06 '21 18:09 domluna

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?

Roger-luo avatar Sep 07 '21 04:09 Roger-luo

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

domluna avatar Sep 07 '21 17:09 domluna

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?

Roger-luo avatar Sep 15 '21 09:09 Roger-luo

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

what do you mean?

domluna avatar Sep 15 '21 14:09 domluna

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.

Roger-luo avatar Sep 16 '21 22:09 Roger-luo

oh ic, yeah that should be a sufficient workaround

domluna avatar Sep 16 '21 23:09 domluna