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

WIP: Staged functions #7474

Merged
merged 11 commits into from Sep 24, 2014
Merged

WIP: Staged functions #7474

merged 11 commits into from Sep 24, 2014

Conversation

Keno
Copy link
Member

@Keno Keno commented Jun 30, 2014

This is my work in progress for staged functions (#7311). It almost works, but there's a few things left to discuss.

  • Syntax: @StefanKarpinski hates stagedfunction foo(); end. @JeffBezanson proposed staged foo(); end, though there's the question if we want to reserve that keyword
  • Caching. E.g. consider the following:
julia> stagedfunction foo(a,b)
       println(a,b)
       :(a+b)
       end

julia> foo(1,2)
Int64Int64
3

julia> foo(1,2)
3

julia> bar() = foo(1,2)
bar (generic function with 1 method)

julia> bar()
Int64Int64
Int64Int64
3

julia> bar()
3

julia> baz() = foo(1,2)
baz (generic function with 1 method)

julia> baz()
Int64Int64
Int64Int64
3

Hopefully calling the staged function that many times can be avoided. In any case, this point isn't critical.

  • Staged functions throwing errors. I'm not handling this yet. In the runtime case this mostly works already, but for type inference we need to intercept and return Any/NF.
  • Documentation

Thanks to @timholy and @jakebolewski for hacking on this with me on Saturday.

@JeffBezanson
Copy link
Sponsor Member

I don't think we should use staged. Not worth taking a dictionary word for this.

@@ -690,7 +697,7 @@ function abstract_call_gf(f, fargs, argtypes, e)
end
end
for (m::Tuple) in x
linfo = m[3].func.code
linfo = func_for_method(m[3],argtypes)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right approach. It means _methods does not return anything useful unless you know how to fix up its result. We can change what _methods returns instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. Any thoughts on what _methods should return? Just the lambda info rather than the method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though if we change it to not return the method, then _methods as a name doesn't really make a whole lot of sense any more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeffBezanson thoughts on what it should return. Just replace m[3] by the actual lambda info? I can see one instance in which m.sig is being accesses but in all other instances it's just picking out the lambda info.

@timholy
Copy link
Sponsor Member

timholy commented Jun 30, 2014

Hmm, that is odd behavior. It seems to suggest that the specifically-compiled variants are not getting inserted into the methods table properly. What does methods(foo) say after you call foo(1,2)? What does it say at the end after defining bar and baz?

@timholy
Copy link
Sponsor Member

timholy commented Jun 30, 2014

Also, am I right in guessing that more needs to be done to handle splatted arguments?

@JeffBezanson
Copy link
Sponsor Member

A rule of thumb is that the system should behave as if the code generator did not exist, and all of its possible outputs have been written manually in a magic infinite source file.

@Keno
Copy link
Member Author

Keno commented Jun 30, 2014

I'm not sure about splatted arguments. Haven't tried. About the method cache, I think it is being entered correctly (see the second call to foo). The problem is probably inlining.

@JeffBezanson That analogy doesn't really work though, unless you want to add the generated methods to mt->defs and even then they would only be there after being called.

@JeffBezanson
Copy link
Sponsor Member

The rule of thumb should be followed to the extent possible. It's there to resolve design decisions that could go either way.

@timholy
Copy link
Sponsor Member

timholy commented Jul 1, 2014

Looks like we're super-close, but that splatting seems to be a problem. Check this out (we are so close to having awesome array views!):

A = rand(5,5,3);
B = slice(A, 1:3, 2, 1:3);
stagedfunction mygetindex(S::SubArray, indexes::Real...)
    println(S)
    T, N, A, I = S.parameters
    if N != length(indexes)
        error("Wrong number of indexes supplied")
    end
    NP = length(I)
    indexexprs = Array(Expr, NP)
    j = 1
    for i = 1:NP
        println(i)
        println(j)
        println(I[i])
        if I[i] == Int
            indexexprs[i] = :(S.indexes[$i])
        else
            indexexprs[i] = :(S.indexes[$i][indexes[$j]])
            j += 1
        end
    end
    println(indexexprs)
    ex = :(S[$(indexexprs...)])
    println(ex)
    ex
end

julia> mygetindex(B, 2, 2)
SubArray{Float64,2,Array{Float64,3},(UnitRange{Int64},Int64,UnitRange{Int64})}
1
1
UnitRange{Int64}
2
2
Int64
3
2
UnitRange{Int64}
[:(S.indexes[1][indexes[1]]),:(S.indexes[2]),:(S.indexes[3][indexes[2]])]
S[S.indexes[1][indexes[1]],S.indexes[2],S.indexes[3][indexes[2]]]
ERROR: syntax: "Array{Any, 1}" is not a valid function argument name

Ideally the compiled function should have a signature like this:

mygetindex(S::SubArray{Float64,2,Array{Float64,3},(UnitRange{Int64},Int64,UnitRange{Int64})}, indexes_1::Int, indexes_2::Int)

but I think it's just the naming that's causing problems.

@porterjamesj
Copy link
Contributor

💯

just wanted to register my excitement and say it was very cool to see the initial prototype work on Saturday :)

@porterjamesj
Copy link
Contributor

also is this going to make it into 0.3 or will it be an 0.4-pre thing?

@timholy
Copy link
Sponsor Member

timholy commented Jul 1, 2014

Definitely not 0.3 material.

@lstagner
Copy link
Contributor

lstagner commented Jul 1, 2014

While this can legitimately be called a staged function, I feel that the concept of staged functions is much more broad then how they are being applied here. Calling this a stage function would be presumptuous since there could be other functions that fit the definition of what a staged function is.

Since this is a function/macro that operates on types why not have the syntax be

typefunction foo(a,b) #or tfunction foo(a,b)

or

typemacro foo(a,b) #or tmacro foo(a,b)

@IainNZ
Copy link
Member

IainNZ commented Jul 1, 2014

typemacro does have some good things going to for it too on top of that, in that its not likely to be wanted in user code, reduces possible confusion with plain old functions, and is short

@quinnj
Copy link
Member

quinnj commented Jul 1, 2014

typemacro has a nice ring to it. What about call site syntax? Do we want to visually distinguish, like macros, that "something funny is going on here"? I think Jeff mentioned @@foo(a,b) syntax is available for something.

@JeffBezanson
Copy link
Sponsor Member

I fail to see how it's presumptuous to apply a term to something that fits its definition. What would it mean for another design or implementation to be "more staged" than this?

This can't have different syntax; the whole point is that it's part of the behavior of a generic function. Some methods might be staged, others not, transparently.

@JeffBezanson
Copy link
Sponsor Member

I would also be ok with a more concrete, less-jargony name possibly evoking "code generator". or "generated method", or something along those lines.

@quinnj
Copy link
Member

quinnj commented Jul 1, 2014

Ah, I didn't catch that these will mesh with generic functions. Very cool.

@lstagner
Copy link
Contributor

lstagner commented Jul 1, 2014

@JeffBezanson The presumptuous bit is to allocate a piece of syntax for something that describes a family of functions. I would be a bit like a language defining sin(x) as trigfunction(x) . If the stagedfunction syntax could be applied to all types of staged functions it would be fine.

I think the syntax

typemacro foo(a,b)
    println(a,b)
end

foo(1,2)

would be fine since in your initial mailing list post you say

"These are a lot like macros, except they operate on types instead of
expressions. " -Jeff Bezanson

This may however cause confusion with people trying to call them like macros.(which is why I also suggested typefunction)

@JeffBezanson
Copy link
Sponsor Member

I hope we can find a better name. type function or t function is a term of art used elsewhere in the system to refer to functions that operate within the type domain, used by inference.

@lstagner
Copy link
Contributor

lstagner commented Jul 1, 2014

Also something to consider would be something like type annotations but for functions. Like

function foo{typed}(a,b) #or foo::typed(a,b)
end

something that describes what aspects of the arguments are passed into the function e.g. in a normal function their values are passed but in a typed function their types are passed.

@Keno
Copy link
Member Author

Keno commented Jul 1, 2014

Both

function foo{typed}(a,b)

and

foo::typed(a,b)

already have a meaning.

@lstagner
Copy link
Contributor

lstagner commented Jul 1, 2014

@Keno then perhaps something like

function{Type} foo(a,b)

I kinda like this idea. Instead of Type where it passes the argument's type it could be Symbol where it just passes the argument's symbol. Default would where it passes the argument's Value

Anyway I've said my piece. I realize this is a bike shed issue so I am going to shut up about it now and let you experts take care of it.

@StefanKarpinski
Copy link
Sponsor Member

typemacro or type macro (one less keyword plus more readable) strike me as good.

@porterjamesj
Copy link
Contributor

typemacro or similar seems like it might be confusing to some in that you don't invoke these like macros; they just generate functions and lodge them in the method table for you.

@mlubin
Copy link
Member

mlubin commented Jul 1, 2014

typemacro seems confusing on the part of the user, because these functions are called like functions and not with the @ prefix. My vote is for stagedfunction.

@mlubin
Copy link
Member

mlubin commented Jul 1, 2014

jinx @porterjamesj

@yurivish
Copy link
Contributor

yurivish commented Jul 1, 2014

What about genfunction or functiongen? These are like stagedfunction, but are a little shorter and capture the idea that what you're defining is a function generator.

@porterjamesj
Copy link
Contributor

@yurivish +1

@stevengj
Copy link
Member

stevengj commented Jul 1, 2014

@mlubin, since they generate expressions, they are seem more like macros than functions, and hence I like typemacro. Couldn't the patch be changed to call them with @?

@MikeInnes
Copy link
Member

If we're using white space anyway, seems reasonable to just use a macro – @generated function ... or @gen function... (edit: ok, Stefan got there first)

I also quite liked fucro, but that's probably why I'm not making the decisions around here.

@toivoh
Copy link
Contributor

toivoh commented Nov 21, 2014

Oh, forgot about python generators :) (which I indeed hope that we will have at some point)

@StefanKarpinski
Copy link
Sponsor Member

+1 for @generated function, @generated type, etc.

@Jutho
Copy link
Contributor

Jutho commented Nov 21, 2014

Generated types can be implemented right now using the tricks used in #8432. Not perfect, but still very convenient until we have the full thing. In fact, it shouldn't be to hard to implement a general scheme for this using a macro, which could then be called @generate(d). This could provide a unified approach towards generated functions and types. However, what transformation should @generate(d) apply to :function, i.e. the child still needs a name, unless the generated/staged aspect is indicated using the :meta mechanism.

With respect to name: I would also prefer generated if it is a keyword, but most macros seem to have a name which is a verb in the present tense. Also compare to the original @ngenerate macro in Base.Cartesian. No strong preference from my side.

@timholy
Copy link
Sponsor Member

timholy commented Nov 21, 2014

Although @ngenerate is imperative: generate them now, at parsing time.

@StefanKarpinski
Copy link
Sponsor Member

I agree with the argument that it should be @generated. There are two flavors of macro: imperatives and annotations – this is more of an annotation than an imperative.

@JeffBezanson
Copy link
Sponsor Member

I like @generated too. It has all the virtues of generated function plus avoids adding a new keyword. Macro names certainly do not need to be verbs in the present tense. This is not past tense either; it's a participle, as in "functions are generated on demand".

@Jutho
Copy link
Contributor

Jutho commented Nov 21, 2014

On 21 Nov 2014, at 16:18, Tim Holy notifications@github.com wrote:

Although @ngenerate is imperative: generate them now, at parsing time.

Indeed. I agree 100%. +1 for @generated.

@ViralBShah
Copy link
Member

+1 for @generated. No whitespace in keywords is nice too. Visually, the macro syntax it also alerts you that something will run at an earlier stage.

@johnmyleswhite
Copy link
Member

If we make this look like a macro, what will macroexpand produce? I'm not sure I like the idea that this looks like a macro, but doesn't work like other macros.

@amitmurthy
Copy link
Contributor

I agree with John. The @ convention is that code is generated when it is used. While defining the generator plain keyword macro is used. +1 for generated function.

@tonyhffong
Copy link

How about using an empty curly?

function f{}( ... )
end

No new reserved word. No need to change editor model. The empty curly suggests that, Instead of parametrizing the function outside, we do that inside...

Edit: ok, f{}{ T<: AbstractArray }( x::T ) ... doesn't look quite right. Never mind me.

@MikeInnes
Copy link
Member

@johnmyleswhite The idea isn't to pretend it's a macro, but for it to actually be a macro. @generated would be a very simple macro that inserts some metadata into the (function/type/whatever) expression for the compiler to pick up on.

We have @inline already doing this, so it's not exactly unprecedented (although admittedly @generated would change the semantics a lot more than @inline).

@timholy
Copy link
Sponsor Member

timholy commented Nov 22, 2014

To follow up on @one-more-minute's point, see http://docs.julialang.org/en/latest/devdocs/meta/

@porterjamesj
Copy link
Contributor

I think the crucial point is that it's the definition of a generated function that involves a macro rather than the callsite—end users don't have to care. +1 for @generated from me.

@stevengj
Copy link
Member

Hornéd hat of shame for the lack of documentation on this....

@timholy
Copy link
Sponsor Member

timholy commented Dec 30, 2014

As I believe you noted elsewhere, it's hard to write documentation for something whose very name is likely to change.

@lstagner
Copy link
Contributor

It seems like the consensus that was reached was for calling them generated (functions|types|...) using the @generated (function|type|...) syntax.

@timholy
Copy link
Sponsor Member

timholy commented Dec 31, 2014

I agree, but until the syntax change happens it seems very strange to document it (with either name choice).

@ViralBShah
Copy link
Member

Why not document it with the current name, and then just update the documentation with the name change? I think a lot of people want to use staged functions and learn about them (on master), but going through the issues is a bit cumbersome for many.

@aviks
Copy link
Member

aviks commented Jan 1, 2015

Why not document it with the current name, and then just update the documentation with the name change

I realise this is more work, and I am in no position to help, but I (and I'm sure many others) would really appreciate something on those lines. Maybe even a gist with some docs and examples?

@timholy
Copy link
Sponsor Member

timholy commented Jan 1, 2015

I also confess some reluctance before #8504 gets fixed---it's a nasty problem, and it's holding even me back from using stagedfunctions more pervasively.

But, let's go for it and see what happens. If nothing else it might be more incentive to at least get some discussion about my proposed fix.

@tonyhffong
Copy link

I wonder if we should treat function-typed argument differently in a stagedfunction. When testing types, we most likely want to do method_exists or even code_typed to see if the function supports the input/output signature in the staging phase. Having just Function at this stage isn't that helpful.

That said, it should be illegal to actually call the function-value argument at the staging phase.

@timholy
Copy link
Sponsor Member

timholy commented Jan 9, 2015

It's an interesting idea. Can you expand on your goal, why checking the signature during staging is better than just trying it and seeing what error you get?

But one point is that in principle your suggestion could provide a mechanism for "function-valued argument inlining" (#3440 and perhaps #1864).

@tonyhffong
Copy link

I'm playing with the idea of Functors and Monads using Traits.jl and notice the ideas are not trivial. (PR# mauro3/Traits.jl#4 (comment)). I was trying to see if I can generate associated types with the package. Let's take Functor. A Functor must support a fmap function. In Haskell it imposes that fmap :: (a->b) -> f a -> f b. So we would write something like this:

using Traits

immutable FuncFullSig{TI, TO}
    f::Function
end

@traitdef Functor{X{Y}} begin
    fmap( FuncFullSig{Y,Z}, X{Y} ) -> X{Z}
end

So when we do

a = Float64[1.0,2.0] # assume we assert Array is a Functor
b = fmap( FuncFullSig{Float64,Float64}( sin ), a ) # b should be provably Array{Float64}

It would do away with many boilerplate codes, as many types are just Functors in disguise (Nullable, Associative{K}, DataArray, etc.) . But writing FuncFullSig is a bit cumbersome. It would be really nice, when we do just b = fmap( sin, c ), the following intermediate steps happen:

  • ask if typeof(c) is a Functor? (already do-able now with Traits.jl)
  • if so, check if the method sin( x::T ) exists? (T is the Functor parameter of typeof(c))
  • (bonus) provide type inference info that b must be a Functor of the result type

Ideally, all these would be better done only once per (Function, ArgTypes...) tuple, or the performance penalty would be massive. Stagedfunction would be an interesting place to put it, but
we are in a strange place right now, when we only know the types of arguments but not the signatures of function arguments, a challenge that is a by-product of julia's multiple-dispatch feature. (Ironically, multiple-dispatch is exactly what makes Traits.jl work.)

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.

None yet